/dev/stdin со строкой

Мне нужен сценарий Bash, который может принимать данные из файла или стандартного ввода, например, так же, как grep

$ 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

Ошибка при использовании строки

$ 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

person Steven Penny    schedule 11.03.2013    source источник
comment
В вашем случае, кстати, это легко обойти, написав if [[ $# = 0 ]] ; then read b ; else read b < "$1" ; fi. Но я понятия не имею, зачем нужен такой обходной путь.   -  person ruakh    schedule 11.03.2013


Ответы (4)


Использование /dev/stdin таким образом может быть проблематичным, поскольку вы пытаетесь получить дескриптор стандартного ввода, используя имя в файловой системе (/dev/stdin), а не файловый дескриптор, который bash уже передал вам как стандартный ввод (файловый дескриптор 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
person Austin Phillips    schedule 11.03.2013
comment
На самом деле это не так в данном конкретном случае. Bash обрабатывает любой из /dev/fd/* внутри, если он используется либо в перенаправлении, либо в тестовом выражении (именно поэтому они перечислены в руководстве). Если вы используете Bash или ksh93, их можно использовать переносимо, если они не просто передаются в качестве аргументов встроенной или внешней команде. Вы даже можете записать строку в Bash, используя /dev/stdin. Однако не все оболочки используют временные файлы для heredocs. Dash/busybox используют каналы. - person ormaaj; 12.03.2013
comment
@ormaaj Интересно. В руководстве говорится, что /dev/stdin обрабатывается особым образом, но мое первоначальное прочтение исходного кода bash показало, что если /dev/stdin был доступен на этапе настройки, то /dev/stdin будет обрабатываться как любой другой файл. т.е. в исходном коде HAVE_DEV_STDIN будет определено и поэтому не появится в специальном списке имен файлов. - person Austin Phillips; 12.03.2013
comment
Это может быть, но это также не должно иметь большого значения. Это отлично работает здесь, в Linux: dash -c 'x=$(mktemp); echo test >"$x"; { unlink -- "$x"; cat; cat /proc/self/fd/0; } <"$x"' - person ormaaj; 12.03.2013
comment
@ormaaj Хотя это работает в Linux, в Cygwin это не работает. Если вы запустите strace по команде dash, вы увидите, что /proc/self/fd/0 открывается и выделяет свой собственный файловый дескриптор, а не вызов dup. Я не совсем понимаю, но в Linux кажется, что открытие /proc/self/fd/0 -> foo (deleted file) разрешено и возвращает содержимое файла. - person Austin Phillips; 12.03.2013
comment
В Linux echo test > /tmp/foo; { rm /tmp/foo; echo A; cat /tmp/foo; echo B; cat /proc/self/fd/0; } < /tmp/foo дает вывод A cat: /tmp/foo: No such file or directory B test, показывающий, что /proc/self/fd/0 можно открыть, и выводит исходный текст. На cygwin вывод A cat: /tmp/foo: No such file or directory B cat: /proc/self/fd/0: No such file or directory . Поскольку /proc/ эмулируется только в Cygwin, а не в ядре, это, вероятно, и является причиной разницы. - person Austin Phillips; 12.03.2013
comment
Имеет смысл. Насколько я помню, в списках рассылки обсуждалась эта проблема с Cygwin. Вы не ожидаете, что он будет идентичен dup, иначе результирующий fd будет искаться до конца для второго cat в моем примере. Одна из приятных особенностей /dev/stdin перенаправлений заключается в том, что они дают вам отдельный FD с независимым поиском, тем более что вы не можете lseek в Bash. - person ormaaj; 12.03.2013

bash специально анализирует некоторые имена файлов (например, /dev/stdin), так что они распознаются, даже если они фактически не присутствуют в файловой системе. Если ваш сценарий не имеет #!/bin/bash вверху, а /dev/stdin не находится в вашей файловой системе, ваш сценарий может быть запущен с использованием /bin/sh, что предполагает, что /dev/stdin на самом деле будет файлом.

(Возможно, это должен быть не ответ, а скорее комментарий к ответу Остина.)

person chepner    schedule 11.03.2013
comment
Смотрите мой комментарий к ormaaj. Мне кажется, что /dev/stdin будет обрабатываться только в том случае, если он не существует во время компиляции. Было бы интересно, если бы кто-то еще прочитал код и проверил. - person Austin Phillips; 12.03.2013
comment
Интересно! Я исходил только из того, что написано на справочной странице, и, в частности, никогда не смотрел на использование на машинах с реальной записью в файловой системе и без нее. - person chepner; 12.03.2013

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

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

Для меня не проблема. Я использую bash 4.2.42 в Mac OS X.

person zzk    schedule 11.03.2013

У тебя тут опечатка

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

Пытаться

read b < "${1:-/dev/stdin}"
person djoot    schedule 11.03.2013
comment
Оба обозначения приемлемы; оболочка справляется с обоими. Работает ли версия без двоеточия так, как задумано, более сомнительно. - person Jonathan Leffler; 11.03.2013
comment
${1-/dev/stdin} заменяет $1 на /dev/stdin, только если $1 не установлено. ${1:-/dev/stdin} также заменит $1, если для него задана пустая строка. - person chepner; 11.03.2013
comment
Интересно, не могу найти ссылки на расширение ${параметр-слово} на справочных страницах bash. Но это работает, как сказал Чепнер... Плохо. - person djoot; 11.03.2013
comment
Документация расплывчата; на странице руководства есть одно предложение непосредственно перед списком различных операторов, описывающее эффект отсутствия двоеточия в любом из них. Его трудно искать, и очень легко пропустить, если вы просто бегло пролистываете. - person chepner; 11.03.2013