2013-03-11 20 views
5

我想一個bash腳本,可以輸入從文件或標準輸入,就像grep,例如的/ dev /標準輸入與herestring

$ cat hw.txt 
Hello world 

$ grep wor hw.txt 
Hello world 

$ echo 'Hello world' | grep wor 
Hello world 

$ grep wor <<< 'Hello world' 
Hello world 

所有精美的作品。然而,隨着下面的腳本

read b < "${1-/dev/stdin}" 
echo $b 

,如果使用herestring

$ hw.sh hw.txt 
Hello world 

$ echo 'Hello world' | hw.sh 
Hello world 

$ hw.sh <<< 'Hello world' 
/opt/a/hw.sh: line 1: /dev/stdin: No such file or directory 
+1

在你的情況,順便說一句,可以很容易地解決這個寫'如果[$#= 0 ]];然後讀b;否則讀b <「$ 1」; fi'。但我不知道爲什麼這樣的解決方法應該是必要的。 – ruakh 2013-03-11 02:58:31

回答

7

以這種方式使用/dev/stdin可能是有問題的,因爲你正試圖獲得一個句柄在文件系統(/dev/stdin)使用一個名稱,標準輸入,而不是使用文件描述符哪個bash已經把你作爲stdin(文件描述符0)交給你。

這裏有一個小的腳本爲您測試:

#!/bin/bash 

echo "INFO: Listing of /dev" 
ls -al /dev/stdin 

echo "INFO: Listing of /proc/self/fd" 
ls -al /proc/self/fd 

echo "INFO: Contents of /tmp/sh-thd*" 
cat /tmp/sh-thd* 

read b < "${1-/dev/stdin}" 
echo "b: $b" 

在我的Cygwin安裝這將產生以下:

./s <<< 'Hello world' 


$ ./s <<< 'Hello world' 
INFO: Listing of /dev 
lrwxrwxrwx 1 austin None 15 Jan 23 2012 /dev/stdin -> /proc/self/fd/0 
INFO: Listing of /proc/self/fd 
total 0 
dr-xr-xr-x 2 austin None 0 Mar 11 14:27 . 
dr-xr-xr-x 3 austin None 0 Mar 11 14:27 .. 
lrwxrwxrwx 1 austin None 0 Mar 11 14:27 0 -> /tmp/sh-thd-1362969584 
lrwxrwxrwx 1 austin None 0 Mar 11 14:27 1 -> /dev/tty0 
lrwxrwxrwx 1 austin None 0 Mar 11 14:27 2 -> /dev/tty0 
lrwxrwxrwx 1 austin None 0 Mar 11 14:27 3 -> /proc/5736/fd 
INFO: Contents of /tmp/sh-thd* 
cat: /tmp/sh-thd*: No such file or directory 
./s: line 12: /dev/stdin: No such file or directory 
b: 

什麼這個輸出顯示的是bash的是創建一個臨時文件來保存你的HERE文件(/tmp/sh-thd-1362969584),並使其在文件描述符0,stdin上可用。但是,臨時文件已從文件系統中斷開連接,因此無法通過文件系統名稱(如/dev/stdin)進行參考。您可以通過讀取文件描述符0來獲取內容,但不能嘗試打開/dev/stdin

在Linux上,上面的./s腳本提供了以下,表明該文件已被解除鏈接:使用

INFO: Listing of /dev 
lrwxrwxrwx 1 root root 15 Mar 11 09:26 /dev/stdin -> /proc/self/fd/0 
INFO: Listing of /proc/self/fd 
total 0 
dr-x------ 2 austin austin 0 Mar 11 14:30 . 
dr-xr-xr-x 7 austin austin 0 Mar 11 14:30 .. 
lr-x------ 1 austin austin 64 Mar 11 14:30 0 -> /tmp/sh-thd-1362965400 (deleted) <---- /dev/stdin not found 
lrwx------ 1 austin austin 64 Mar 11 14:30 1 -> /dev/pts/12 
lrwx------ 1 austin austin 64 Mar 11 14:30 2 -> /dev/pts/12 
lr-x------ 1 austin austin 64 Mar 11 14:30 3 -> /proc/10659/fd 
INFO: Contents of /tmp/sh-thd* 
cat: /tmp/sh-thd*: No such file or directory 
b: Hello world 

更改腳本的標準輸入提供的,而不是試圖通過/dev/stdin引用。

if [ -n "$1" ]; then 
    read b < "$1" 
else 
    read b 
fi 
+1

在這種情況下實際情況並非如此。如果在重定向或測試表達式中使用Bash,則內部處理任何'/ dev/fd/*'(這就是爲什麼它們在手冊中列出的原因)。如果你使用的是Bash或ksh93,那麼只要它們不是作爲參數傳遞給內建命令或外部命令,就可以使用它們。你甚至可以使用'/ dev/stdin'寫入一個Bash herestring。但是,並不是所有的shell都爲臨時文件使用臨時文件。 Dash/busybox使用管道。 – ormaaj 2013-03-12 02:23:04

+0

@ormaaj有趣。該手冊指出'的/ dev/stdin'被特殊處理,但我在bash源的初步數據將表明,如果'的/ dev/stdin'在配置階段是可用的,那麼'的/ dev/stdin'將被作爲治療任何其他文件。即在源代碼中,'HAVE_DEV_STDIN'將被定義,因此不會出現在特殊文件名列表中。 – 2013-03-12 02:50:44

+0

這可能是,但它也不應該真的很重要。這在Linux上可以正常工作:'dash -c'x = $(mktemp);回聲測試>「$ x」; {unlink - 「$ x」;貓; cat/proc/self/fd/0; } <「$ x」'' – ormaaj 2013-03-12 04:37:17

0
$ cat ts.sh 
read b < "${1-/dev/stdin}" 
echo $b 

$ ./ts.sh <<< 'hello world' 
hello world 

沒問題,我會失敗。我使用bash 4.2.42在Mac OS X

-1

你來到這裏

read b < "${1-/dev/stdin}" 

一個錯字嘗試

read b < "${1:-/dev/stdin}" 
+1

這兩種表示法都可以接受;外殼處理兩者。沒有冒號的版本是否按預期工作是更可疑的。 – 2013-03-11 05:24:46

+1

如果未設置$ 1,'$ {1-/dev/stdin}'只會將「$ 1」替換爲「/ dev/stdin」。如果設置爲空字符串,'$ {1: -/dev/stdin}'也會替換$ 1。 – chepner 2013-03-11 13:00:29

+1

有趣的是,在bash手冊頁中找不到任何對$ {parameter-word}擴展的引用。但它像chepner說...我的壞。 – djoot 2013-03-11 14:41:53

1

bash解析某些文件的名字(例如/dev/stdin)特別,使他們即使他們不是實際存在的認可在文件系統中。如果您的腳本頂部沒有#!/bin/bash,並且/dev/stdin不在您的文件系統中,那麼您的腳本可能會使用/bin/sh運行,這會使/dev/stdin實際上成爲一個文件。

(這應該,也許,不是一個答案,而是Austin's answer評論。)

+1

查看我對ormaaj的評論。在我看來,'/ dev/stdin'只有在編譯時不存在的情況下才會被專門處理。會有興趣讓其他人閱讀代碼並進行驗證。 – 2013-03-12 02:55:27

+1

有趣!我只根據手冊頁說的內容去做,特別是從來沒有真正看過帶有或沒有真正文件系統條目的機器上的使用情況。 – chepner 2013-03-12 12:24:18