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


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.