Чтение значений в переменную оболочки из трубы
Я пытаюсь получить 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)