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


205

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


1
Ваш пример .. эхо "Привет, мир" | читать тест; echo test = $ test для меня работал нормально .. результат: test = hello world; в какой среде это работает? Я использую Bash 4.2 ..
alex.pilon

Вы хотите несколько строк в одном чтении? Ваш пример показывает только одну строку, но описание проблемы неясно.
Чарльз Даффи

2
@ alex.pilon, у меня Bash версии 4.2.25, и его пример у меня тоже не работает. Может быть, это вопрос времени выполнения Bash или переменной среды? У меня пример с Sh тоже не работает, так может быть Bash может попытаться быть совместимым с Sh?
Hibou57

2
@ Hibou57 - я попробовал это снова в bash 4.3.25, и он больше не работает. Моя память размыта из-за этого, и я не уверен, что я мог сделать, чтобы заставить его работать.
alex.pilon

2
@ Hibou57 @ alex.pilon последний cmd в трубе должен влиять на переменные в bash4> = 4.2 с shopt -s lastpipe- tldp.org/LDP/abs/html/bashver4.html#LASTPIPEOPT
imz - Иван Захарящев

Ответы:


162

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

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 - это отличная коллекция, необходимая для борьбы со странностями и несовместимостью борновых оболочек, ш.


Вы можете сделать назначение последним, выполнив это вместо этого: `test =` `echo" hello world "| {читать тест; echo $ test; } `` `
Compholio

2
Давайте попробуем это снова ( по- видимому , вылетающие кавычку в этой разметке весело):test=`echo "hello world" | { read test; echo $test; }`
Compholio

могу ли я спросить, почему вы использовали {}вместо ()группировки этих двух команд?
Юрген Паул

4
Хитрость заключается не в том, чтобы readсделать входные данные из канала, а в том, чтобы использовать переменную в той же оболочке, которая выполняет read.
Chepner

1
Получил bash permission deniedпри попытке использовать это решение. Мой случай совершенно другой, но я не мог найти ответ на него в любом месте, что работает для меня было (другой пример , но подобное использование): pip install -U echo $(ls -t *.py | head -1). В случае, если кто-то когда-либо сталкивался с подобной проблемой и наткнулся на этот ответ, как я.
ivan_bilan

111

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

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 Larry Wall et al.


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

43

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

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

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


Хороший и простой однострочник, легко понять. Этот ответ нуждается в большем количестве голосов.
Дэвид Паркс

42

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

$ read test < <(echo hello world)

$ echo $test
hello world

24
Существенное преимущество , которое <(..)имеет более чем $(..)является то , что <(..)возвращает каждую строку к абоненту , как только команда , что она выполняет делает его доступным. $(..)однако ожидает завершения команды и генерации всех ее выходных данных, прежде чем она сделает любой вывод доступным для вызывающей стороны.
Дерек Махар

37

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

stdin=$(cat)

echo "$stdin"

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

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

4
Вероятно, это связано с тем, что read - это команда bash, а cat - это отдельный двоичный файл, который будет запущен в подпроцессе, поэтому он менее эффективен.
dj_segfault

10
иногда простота и ясность превосходят эффективность :)
Rondo

5
безусловно, самый простой ответ
drwatsoncode

Проблема, с которой я столкнулся, заключается в том, что если канал не используется, скрипт зависает.
Дейл Андерсон

@DaleA Ну, конечно. Любая программа, которая пытается читать со стандартного ввода, будет «зависать», если на нее ничего не подается.
Джановски

27

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

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

2
ах! так хорошо, это. при тестировании в интерактивной оболочке также: «set + m» (не требуется в сценарии .sh)
XXL

14

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

var=$(command)

или

var=`command`

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


4
Потому что $ () может быть легко вложен. Думайте в JAVA_DIR = $ (dirname $ (readlink -f $ (которая java))) и попробуйте с `. Вам нужно будет убежать три раза!
Альбфан

8

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

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

и вывод:

тест = привет мир

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

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


8

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

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

1
Я почти сошел с ума, прежде чем нашел это. Большое спасибо за обмен!
Сэми Диндан,

6

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

5

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

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

Вывод:

$ 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 [[ -pможет проверить, что это труба или нет.

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

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


Вы также можете использовать /dev/stdinссылку на/proc/self/fd/0
Elie G.

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внутренний скрипт.


1

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

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

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

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

cat program_file.c | line 34

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

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 ~ $ 

вот и ты.


-1

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

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

В основном обман из моего ответа ниже.
Джановски

Может быть, я бы сказал, что мой немного чище и ближе к коду, опубликованному в оригинальном вопросе.
bingles
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.