2013-03-11 27 views
5

Chciałbym skryptu bash, które mogą pobierać dane z pliku lub standardowego wejścia, podobnie jak grep, na przykład/dev/stdin z 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 

wszystko pięknie działa. Jednak z poniższego skryptu

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

zawiedzie jeśli używając 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

W twoim przypadku, nawiasem mówiąc, jest to łatwe do obejścia tego pisząc 'if [[$ # = 0 ]]; następnie przeczytaj b; jeszcze przeczytać b <"$ 1"; fi'. Ale nie mam pojęcia, dlaczego takie obejście powinno być konieczne. – ruakh

Odpowiedz

7

Używanie /dev/stdin w ten sposób może być problematyczne, ponieważ próbujesz uzyskać uchwyt do stdin używając nazwy w systemie plików (/dev/stdin) zamiast używać deskryptora pliku który bash podał już ciebie jako stdin (deskryptor pliku 0).

Oto mały skrypt do testowania:

#!/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" 

Na mojej instalacji cygwin ten produkuje następujące:

./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: 

co to wyjściowe pokazuje, że bash tworzy plik tymczasowy trzymać Twój dokument TUTAJ (/tmp/sh-thd-1362969584) i udostępnienie go na deskryptorze pliku 0, stdin. Jednak plik tymczasowy został już odłączony od systemu plików i dlatego nie jest dostępny przez odniesienie poprzez nazwę systemu plików, taką jak /dev/stdin. Możesz uzyskać zawartość, czytając deskryptor pliku 0, ale nie próbując otworzyć /dev/stdin.

W systemie Linux, skrypt ./s powyżej daje następujące, pokazując, że plik został odłączony:

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 

Zmień skrypt do korzystania z stdin dostarczone, zamiast próbować odwoływać poprzez /dev/stdin.

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

To w rzeczywistości nie jest prawdą w tym konkretnym przypadku.Bash obsługuje wewnętrznie '/ dev/fd/*', jeśli jest używany w przekierowaniu lub w wyrażeniu testowym (dlatego są wymienione w instrukcji). Pod warunkiem, że używasz Bash lub ksh93, można ich używać przenośnie, o ile nie są one dostarczane jako argumenty wbudowanego lub zewnętrznego polecenia. Możesz nawet pisać do Basha tutaj, używając '/ dev/stdin'. Jednak nie wszystkie powłoki używają plików tymczasowych dla heredoc. Dash/busybox używają rur. – ormaaj

+0

@ormaaj Ciekawe. Instrukcja stwierdza, że ​​'/ dev/stdin' jest obsługiwany specjalnie, ale mój początkowy odczyt źródła bash wskazuje, że jeśli'/dev/stdin' był dostępny podczas fazy konfiguracji, to '/ dev/stdin' byłby traktowany jako każdy inny plik. tzn. w źródle zdefiniowano by "HAVE_DEV_STDIN", a więc nie pojawiłoby się na liście nazw plików specjalnych. –

+0

To może być, ale tak naprawdę nie powinno mieć znaczenia. Działa to dobrze w Linuksie: 'dash -c 'x = $ (mktemp); test echa> "$ x"; {unlink - "$ x"; kot; cat/proc/self/fd/0; } <"$ x" '' – ormaaj

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

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

dla mnie żadnego problemu. Używam bota 4.2.42 na Mac OS X.

-1

Masz literówkę tutaj

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

Spróbuj

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

Oba oznaczenia są dopuszczalne; powłoka obsługuje oba. To, czy wersja bez dwukropka działa zgodnie z zamierzeniami, jest bardziej wątpliwe. –

+1

'$ {1-/dev/stdin}' zastępuje '$ 1' przez"/dev/stdin ", jeśli' $ 1' jest unieważnione. '$ {1: -/dev/stdin}' również zastąpi '$ 1', jeśli jest ustawiony na pusty ciąg znaków. – chepner

+1

Interesujące, nie można znaleźć żadnego odniesienia do rozszerzenia $ {parameter-word} na stronach Bash Man. Ale działa jak chepner powiedział ... Mój zły. – djoot

1

bash analizuje niektóre nazwy plików (jak /dev/stdin) specjalnie tak, że są one rozpoznawane nawet jeśli nie są one rzeczywiście obecny w systemie plików. Jeśli Twój skrypt nie ma wartości #!/bin/bash u góry, a /dev/stdin nie znajduje się w systemie plików, skrypt może być uruchomiony przy użyciu /bin/sh, co może spowodować, że /dev/stdin będzie plikiem.

(Powinno być może nie być odpowiedzią, ale raczej komentarz do Austin's answer.)

+1

Zobacz mój komentarz do ormaaj. Wydaje mi się, że '/ dev/stdin' będzie traktowany tylko wtedy, gdy nie istnieje w czasie kompilacji. Byłby zainteresowany, aby ktoś inny przeczytał kod i zweryfikował. –

+1

Interesujące! Pominąłem tylko to, co mówi strona podręcznika, a szczególnie nigdy nie patrzyłem na użycie na maszynach z rzeczywistym wejściem do systemu plików i bez niego. – chepner