Чтение значений в переменную оболочки из трубы

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

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. Я пробовал поставить "quotes" вокруг "$test" который тоже не работает.

Ответ 1

Использование

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

Вы можете обмануть read для приема из этого канала следующим образом:

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

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

read_from_pipe() { read "[email protected]" <&0; }

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

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

Ответ 2

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

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 Ларри Уолла и др.

Ответ 3

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

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

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

Ответ 4

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

$ read test < <(echo hello world)

$ echo $test
hello world

Ответ 5

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

stdin=$(cat)

echo "$stdin"

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

$ fortune | eval 'stdin=$(cat); echo "$stdin"'

Ответ 6

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

shopt -s lastpipe
echo "hello world" | read test; echo test=$test

Ответ 7

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

var=$(command)

или

var=`command`

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

Ответ 8

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

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

а выход:

test = hello world

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

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

Ответ 9

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

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

Ответ 10

Потому что я влюбился в это, я хотел бы оставить заметку. Я нашел эту ветку, потому что мне нужно переписать старый sh script для совместимости с 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 печатает пустую строку. В старой записи выполняется тело while и в нотации 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

Ответ 11

Связывание чего-либо с выражением, связанным с назначением, не ведет себя так.

Вместо этого попробуйте:

test=$(echo "hello world"); echo test=$test

Ответ 12

Следующий код:

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

тоже будет работать, но после него откроется еще одна новая оболочка, где

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

не будет.


Мне пришлось отключить управление заданиями, чтобы использовать метод chepnars (я запускал эту команду из терминала):

set +m;shopt -s lastpipe
echo "hello world" | read test; echo test=$test
echo "hello world" | test="$(</dev/stdin)"; echo test=$test

Bash Руководство говорит:

lastpipe

Если установлено, и управление заданиями не активно, оболочка запускает последнюю команду конвейера, не выполняемого в фоновом режиме в текущей оболочке окружающая среда.

Примечание. Управление заданием по умолчанию отключено в неинтерактивной оболочке и, следовательно, вам не нужен set +m внутри script.

Ответ 13

Я думаю, вы пытались написать оболочку script, которая могла бы принимать входные данные из stdin. но пока вы пытаетесь сделать это inline, вы потерялись, пытаясь создать эту test = variable. Я думаю, что нет смысла делать это inline, и почему он не работает так, как вы ожидаете.

Я пытался уменьшить

$( ... | head -n $X | tail -n 1 )

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

cat program_file.c | line 34

поэтому мне нужна небольшая программа оболочки, способная читать из stdin. как вы.

22:14 ~ $ cat ~/bin/line 
#!/bin/sh

if [ $# -ne 1 ]; then echo enter a line number to display; exit; fi
cat | head -n $1 | tail -n 1
22:16 ~ $ 

там вы идете.

Ответ 14

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

eval $(echo hickery dickery dock the mouse | { read A B C ; echo A=\"${A}\" B=\"${B}\" C=\"${C}\" ; })
echo $A
hickery
echo $B
dickery
echo $C
dock the mouse

Ответ 15

Интеллектуальный скрипт, который может считывать данные из PIPE и аргументы командной строки:

#!/bin/bash
if [[ -p /proc/self/fd/0 ]]
    then
    PIPE=$(cat -)
    echo "PIPE=$PIPE"
fi
echo "[email protected]"

Выход:

$ bash test arg1 arg2
ARGS=arg1 arg2

$ echo pipe_data1 | bash test arg1 arg2
PIPE=pipe_data1
ARGS=arg1 arg2

Объяснение: Когда скрипт получает какие-либо данные через канал, stdin/proc/self/fd/0 будет символической ссылкой на канал.

/proc/self/fd/0 -> pipe:[155938]

Если нет, он будет указывать на текущий терминал:

/proc/self/fd/0 -> /dev/pts/5

Опция bash if -p может проверить, что это труба или нет.

cat - читает из stdin.

Если мы используем cat - когда нет стандартного stdin, он будет ждать вечно, поэтому мы помещаем его в условие if.

Ответ 16

Как насчет этого:

echo "hello world" | echo test=$(cat)