Чтение значений в переменную оболочки из канала - PullRequest
180 голосов
/ 30 апреля 2010

Я пытаюсь получить bash для обработки данных со стандартного ввода, который попадает в канал, но безуспешно. Я имею в виду не одну из следующих работ:

echo "hello world" | test=($(< /dev/stdin)); echo test=$test
test=

echo "hello world" | read test; echo test=$test
test=

echo "hello world" | test=`cat`; echo test=$test
test=

где я хочу, чтобы вывод был test=hello world. Я пытался поместить "" кавычки вокруг "$test", что тоже не работает.

Ответы [ 15 ]

151 голосов
/ 21 июля 2011

Используйте

IFS= read var << EOF
$(foo)
EOF

Вы можете обманом read принять трубку, как это:

echo "hello world" | { read test; echo test=$test; }

или даже напишите такую ​​функцию:

read_from_pipe() { read "$@" <&0; }

Но нет никакого смысла - ваши переменные могут не сохраняться! Конвейер может порождать подоболочку, где среда наследуется по значению, а не по ссылке. Вот почему read не беспокоится о вводе из канала - он не определен.

К вашему сведению, http://www.etalabs.net/sh_tricks.html - это отличная коллекция отрядов, необходимых для борьбы с странностями и несовместимостью борновых оболочек, ш.

105 голосов
/ 01 мая 2010

если вы хотите читать много данных и работать с каждой строкой отдельно, вы можете использовать что-то вроде этого:

cat myFile | while read x ; do echo $x ; done

если вы хотите разбить строки на несколько слов, вместо x можно использовать несколько переменных, например:

cat myFile | while read x y ; do echo $y $x ; done

альтернативно:

while read x y ; do echo $y $x ; done < myFile

Но как только вы захотите сделать что-нибудь действительно умное с подобными вещами, вам лучше перейти на некоторый язык сценариев, такой как perl, где вы можете попробовать что-то вроде этого:

perl -ane 'print "$F[0]\n"' < myFile

Существует довольно крутая кривая обучения с Perl (или, я думаю, на любом из этих языков), но в долгосрочной перспективе вам будет намного проще, если вы захотите сделать что-нибудь, кроме простейших скриптов. Я бы порекомендовал Perl Cookbook и, конечно же, язык программирования Perl Ларри Уолла и др.

37 голосов
/ 30 апреля 2010

read не будет читать из канала (или, возможно, результат будет потерян, потому что канал создает подоболочку). Однако вы можете использовать здесь строку в Bash:

$ read a b c <<< $(echo 1 2 3)
$ echo $a $b $c
1 2 3

Но см. Ответ @ chepner для получения информации о lastpipe.

36 голосов
/ 19 июня 2012

Это еще один вариант

$ read test < <(echo hello world)

$ echo $test
hello world
29 голосов
/ 04 октября 2012

Я не эксперт в Bash, но мне интересно, почему это не было предложено:

stdin=$(cat)

echo "$stdin"

Однострочное доказательство того, что у меня работает:

$ fortune | eval 'stdin=$(cat); echo "$stdin"'
21 голосов
/ 02 октября 2013

bash 4.2 представляет опцию lastpipe, которая позволяет вашему коду работать так, как написано, выполняя последнюю команду в конвейере в текущей оболочке, а не подоболочку.

shopt -s lastpipe
echo "hello world" | read test; echo test=$test
12 голосов
/ 30 апреля 2010

Синтаксис для неявного канала из команды оболочки в переменную bash:

var=$(command)

или

var=`command`

В ваших примерах вы передаете данные в оператор присваивания, который не ожидает ввода.

6 голосов
/ 02 августа 2013

Первая попытка была довольно близка. Этот вариант должен работать:

echo "hello world" | { test=$(< /dev/stdin); echo "test=$test"; };

и вывод:

test = hello world

Вам нужны фигурные скобки после трубы, чтобы заключить назначение для проверки и эхо.

Без фигурных скобок назначение для теста (после канала) находится в одной оболочке, а эхо "test = $ test" находится в отдельной оболочке, которая не знает об этом назначении. Вот почему вы получили «test =» в выводе вместо «test = hello world».

5 голосов
/ 09 октября 2016

Поскольку я влюбляюсь в это, я хотел бы оставить записку. Я нашел эту тему, потому что я должен переписать старый скрипт sh быть POSIX-совместимым. По сути, это означает, что можно обойти проблему канала / подоболочки, представленную POSIX, переписав код, подобный следующему:

some_command | read a b c

в

read a b c << EOF
$(some_command)
EOF

И такой код:

some_command |
while read a b c; do
    # something
done

в

while read a b c; do
    # something
done << EOF
$(some_command)
EOF

Но последний не ведет себя так же при пустом вводе. Со старым обозначением цикл while не вводится при пустом вводе, но в нотации POSIX это так! Я думаю, что это из-за перевода строки перед EOF, который не может быть опущен. Код POSIX, который ведет себя больше как старая нотация выглядит так:

while read a b c; do
    case $a in ("") break; esac
    # something
done << EOF
$(some_command)
EOF

В большинстве случаев этого должно быть достаточно. Но, к сожалению, это все еще ведет себя не совсем как старые записи если some_command печатает пустую строку. В старой нотации пока выполняется тело и в нотации POSIX мы ломаемся перед телом.

Подход к решению этой проблемы может выглядеть следующим образом:

while read a b c; do
    case $a in ("something_guaranteed_not_to_be_printed_by_some_command") break; esac
    # something
done << EOF
$(some_command)
echo "something_guaranteed_not_to_be_printed_by_some_command"
EOF
5 голосов
/ 21 июля 2016

На мой взгляд, лучший способ чтения из stdin в bash - это следующий способ, который также позволяет вам работать со строками до окончания ввода:

while read LINE; do
    echo $LINE
done < /dev/stdin
...