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

161

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

  • 0
    Ваш пример .. эхо "Привет, мир" | читать тест; echo test = $ test для меня работал нормально .. результат: test = hello world; в какой среде это работает? Я использую Bash 4.2 ..
  • 0
    Вы хотите несколько строк в одном чтении? Ваш пример показывает только одну строку, но описание проблемы неясно.
Показать ещё 3 комментария
Теги:
pipe

15 ответов

137
Лучший ответ

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

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

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

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

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

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

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

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

  • 0
    Вы можете сделать назначение последним, выполнив это вместо этого: `test =` `echo" hello world "| {прочитать тест; echo $ test; } `` `
  • 2
    Давайте попробуем это еще раз (очевидно, экранирование от обратных галочек в этой разметке - это весело): test=`echo "hello world" | { read test; echo $test; }`
Показать ещё 5 комментариев
101

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

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

  • 8
    «в качестве альтернативы» это правильный путь. Нет UUoC и нет подоболочки. Смотрите BashFAQ / 024 .
34

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

$ read test < <(echo hello world)

$ echo $test
hello world
  • 19
    Существенное преимущество, которое <(..) имеет над $(..) состоит в том, что <(..) возвращает каждую строку вызывающей стороне, как только выполняемая команда делает ее доступной. $(..) , однако, ожидает завершения команды и генерации всех ее выходных данных, прежде чем она сделает какие-либо выходные данные доступными для вызывающей стороны.
33

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

$ read a b c <<< $(echo 1 2 3)
$ echo $a $b $c
1 2 3
  • 0
    Хороший и простой однострочник, легко понять. Этот ответ нуждается в большем количестве голосов.
26

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

stdin=$(cat)

echo "$stdin"

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

$ fortune | eval 'stdin=$(cat); echo "$stdin"'
  • 4
    Возможно, это связано с тем, что read - это команда bash, а cat - это отдельный двоичный файл, который будет запущен в подпроцессе, поэтому он менее эффективен.
  • 2
    Помог мне решить связанную проблему - спасибо
Показать ещё 7 комментариев
17

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

shopt -s lastpipe
echo "hello world" | read test; echo test=$test
  • 0
    ах! так хорошо, это. при тестировании в интерактивной оболочке также: «set + m» (не требуется в сценарии .sh)
11

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

var=$(command)

или

var=`command`

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

  • 6
    Первая форма, var = $ (команда), является предпочтительной.
  • 0
    По какой причине?
Показать ещё 2 комментария
5

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

while read LINE; do
    echo $LINE
done < /dev/stdin
  • 1
    Я почти сошел с ума, прежде чем нашел это. Большое спасибо за обмен!
  • 1
    Этот сделал свое дело !!! Спасибо чувак
4

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

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

а выход:

test = hello world

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

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

3

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

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

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

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

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

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.

1

Я думаю, вы пытались написать оболочку 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 ~ $ 

там вы идете.

0

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

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
0

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

echo "hello world" | echo test=$(cat)
  • 0
    В основном обман из моего ответа ниже.
  • 0
    Может быть, я бы сказал, что мой немного чище и ближе к коду, опубликованному в оригинальном вопросе.

Ещё вопросы

Сообщество Overcoder
Наверх
Меню