Прочитайте значення в змінній оболонки з труби


205

Я намагаюся змусити баш обробляти дані зі 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. Я спробував поставити "" цитати, "$test"які теж не працюють.


1
Ваш приклад .. відлуння "привіт світ" | тест для читання; ехо-тест = $ тест для мене добре працював .. результат: тест = привіт світ; під яким середовищем це працює? Я використовую 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 в трубі повинен впливати на vars в 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не турбує вхід з труби - це не визначено.

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


Ви можете виконати завдання останньою, замість цього: `test =` `echo" привіт світ "| {читати тест; echo $ test; } `` `
Compholio

2
Спробуємо ще раз (мабуть, уникнути test=`echo "hello world" | { read test; echo $test; }`
задніх посилань

чи можу я запитати, чому ви використовували {}замість того, ()щоб групувати ці дві команди?
Юрген Пол

4
Хитрість полягає не в тому, щоб readзробити вхід з труби, а у використанні змінної в тій же оболонці, що виконує read.
чепнер

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 і, звичайно, мову програмування Perl від Ларрі Уолла та ін.


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
іноді простота та чіткість козирної ефективності :)
Рондо

5
безумовно, найпростіша відповідь
drwatsoncode

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

@DaleA Ну, звичайно. Будь-яка програма, яка намагається прочитати зі стандартного вводу, буде «зависати», якщо до неї нічого не подано.
djanowski

27

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

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

2
ах! так добре, це. якщо тестування в інтерактивній оболонці, також: "встановити + m" (не потрібно в .sh-скрипті)
XXL

14

Синтаксис неявної труби з команди оболонки в змінну bash є

var=$(command)

або

var=`command`

У своїх прикладах ви передаєте дані до оператора присвоєння, який не очікує жодного введення.


4
Тому що $ () можна легко вкласти. Подумайте в JAVA_DIR = $ (dirname $ (readlink -f $ (яка Java))), і спробуйте це з `. Вам потрібно буде втекти тричі!
albfan

8

Перша спроба була досить близькою. Цей варіант повинен працювати:

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

а вихід:

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

Вам потрібні дужки після труби, щоб укласти завдання для тестування та відлуння.

Без дужок, завдання тестувати (після труби) знаходиться в одній оболонці, а луна "test = $ test" - в окремій оболонці, яка не знає про це завдання. Ось чому ви отримували "test =" у виході замість "test = привіт світ".


8

На моїх очах найкращий спосіб прочитати з stdin in bash - це наступний, який також дозволяє працювати над рядками до завершення введення:

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

1
Я майже сходив з розуму, перш ніж це виявити. Велике спасибі за спільний доступ!
Samy Dindane

6

Оскільки я підпадаю під це, я хотів би залишити ноту. Я знайшов цю тему, тому що я повинен переписати старий скрипт 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

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

Пояснення: Коли сценарій отримує будь-які дані через pipe, то 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; }

не буде.


Мені довелося відключити контроль роботи, щоб використовувати метод чепнерів (я виконував цю команду з терміналу):

set +m;shopt -s lastpipe
echo "hello world" | read test; echo test=$test
echo "hello world" | test="$(</dev/stdin)"; echo test=$test

Посібник Баша говорить :

lastpipe

Якщо встановлено, а контроль роботи не активний , оболонка виконує останню команду конвеєра, не виконану у фоновому режимі в поточному середовищі оболонки.

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


1

Я думаю, ви намагалися написати скрипт оболонки, який міг би взяти вклад з stdin. але поки ви намагаєтеся зробити це вбудованим, ви заблукали, намагаючись створити тест = змінну. Я думаю, що це не має особливого сенсу робити це в режимі 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 ~ $ 

ось так.


-1

Як щодо цього:

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

В основному дуп з моєї відповіді нижче.
djanowski

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