2010-04-30 4 views
149

Я пытаюсь получить Баш обрабатывать данные из стандартного ввода, который получает по конвейеру, но не повезло, что я имею в виду никого из следующих работ: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. Примечание. Я попытался поставить «quotes» около "$test", который тоже не работает.

+0

Ваш пример .. эхо «привет мир» | прочитать тест; echo test = $ test работал отлично для меня .. результат: test = hello world; в какой среде это работает? Я использую bash 4.2 .. –

+0

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

+1

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

ответ

131

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

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

Вы можете трюк read в принятии из трубы, как это:

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

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

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

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

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

+0

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

+2

Давайте попробуем это снова (видимо, избегая обратных ссылок в этой разметке, это весело): ' test = \ 'echo" hello world "| {прочитать тест; echo $ test; } \ '' – Compholio

+0

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

3

Трубопровод что-то в выражении с назначением не ведет себя так.

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

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

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

var=$(command) 

или

var=`command` 

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

+6

Предпочтительна первая форма, var = $ (команда). –

+0

По какой причине? – rneatherway

+3

Потому что $() может быть легко вложен. Подумайте в JAVA_DIR = $ (dirname $ (readlink -f $ (который java))) и попробуйте его с помощью '. Вам нужно бежать три раза! – albfan

31

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

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

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

cat myFile | while read x ; do echo $x ; done 

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

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

+6

«альтернативно» - правильный путь. Нет UUoC и без подоболочки. См. [BashFAQ/024] (http://mywiki.wooledge.org/BashFAQ/024). –

28

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

$ read test < <(echo hello world) 

$ echo $test 
hello world 
+13

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

1

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

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

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

там вы идете.

26

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

stdin=$(cat) 

echo "$stdin" 

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

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

Это, вероятно, потому, что «чтение» - это команда bash, а cat - отдельный двоичный файл, который будет запущен в подпроцессе, поэтому он менее эффективен. –

+2

Помогло решить проблему - thx – pagid

+6

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

4

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

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

и выход:

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

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

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

17

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

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

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

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 Manual says:

lastpipe

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

Примечание: управление заданием по умолчанию отключено в неинтерактивной оболочке, и поэтому вам не нужен set +m внутри сценария.

0

Как об этом:

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

В принципе, обман из моего ответа ниже. – djanowski

4

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

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

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

+0

Это сделало трюк !!! Спасибо чувак –

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 
1

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