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


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.