Захоплення кількох лінійних виходів у змінну Bash


583

У мене є сценарій "myscript", який видає наступне:

abc
def
ghi

іншим сценарієм я закликаю:

declare RESULT=$(./myscript)

і $RESULTотримує значення

abc def ghi

Чи є спосіб зберегти результат або з новими рядками, або з символом '\ n', щоб я міг вивести його за допомогою ' echo -e'?


1
це мене дивує. у вас немає $ (cat ./myscipt)? в іншому випадку я б очікував, що він спробує виконати команди abc, def та ghi
Йоганнес Шауб - litb

@litb: так, я гадаю, що так; ви також можете використовувати $ (<./ myscript), що уникає виконання команди.
Джонатан Леффлер

2
(Зверніть увагу: два вищезазначені коментарі стосуються перегляду розпочатого питання. У мене з'явився сценарій "myscript", який містить наступне , що призвело до питань. Поточний перегляд питання (у мене є сценарій " таємниця ", яка виводить наступне ) робить коментарі зайвими. Однак, перегляд є з 2011-11-11, довго після того, як були зроблені два коментарі.
Джонатан Леффлер

Відповіді:


1080

Власне, РЕЗУЛЬТАТ містить те, що ви хочете - продемонструвати:

echo "$RESULT"

Що ви показуєте, це те, що ви отримуєте:

echo $RESULT

Як зазначається в коментарях, різниця полягає в тому, що (1) версія з подвійним котируванням змінної ( echo "$RESULT") зберігає внутрішній інтервал значень точно так, як це представлено в змінній - нові рядки, вкладки, кілька пробілів і все - тоді як (2 ) версія без котирування ( echo $RESULT) замінює кожну послідовність одного або декількох пробілів, вкладок та нових рядків одним пробілом. Таким чином, (1) зберігає форму вхідної змінної, тоді як (2) створює потенційно дуже довгий єдиний рядок виводу з "словами", розділеними одинарними пробілами (де "слово" - це послідовність символів, які не є пробілами; не будь-які буквено-цифрові слова в будь-якому із слів).


69
@troelskn: різниця полягає в тому, що (1) версія з подвійним котируванням змінної зберігає внутрішній інтервал значень саме таким, яким він представлений в змінній, нових рядках, вкладках, декількох пробілах і всіх, тоді як (2) версія, що не котирується, замінює кожна послідовність одного або декількох пробілів, вкладок та нових рядків з єдиним пробілом. Таким чином, (1) зберігає форму вхідної змінної, тоді як (2) створює потенційно дуже довгий єдиний рядок виводу з "словами", розділеними одинарними пробілами (де "слово" - це послідовність символів, які не є пробілами; не будь-які буквено-цифрові слова в будь-якому із слів).
Джонатан Леффлер

23
Щоб відповісти було легше зрозуміти: відповідь говорить про те, що відлуння "$ RESULT" зберігає новий рядок, тоді як echo $ RESULT - ні.
Ю. Шень

Це не дозволяє зберегти нові рядки та провідні місця в деяких ситуаціях.
CommaToast

3
@CommaToast: Ви збираєтеся детальніше розглянути це? Затримані нові рядки втрачаються; не існує простого способу цього. Провідні заготовки - я не знаю жодних обставин, за яких вони втрачені.
Джонатан Леффлер


90

Ще однією проблемою є те, що заміна команд - $()- смужки, що перебувають у нових рядках. Напевно, не завжди важливо, але якщо ви дійсно хочете зберегти саме те , що було виведено, вам доведеться скористатися іншим рядком і деяким цитуванням:

RESULTX="$(./myscript; echo x)"
RESULT="${RESULTX%x}"

Це особливо важливо, якщо ви хочете обробити всі можливі імена файлів (щоб уникнути невизначеної поведінки, наприклад роботи з неправильним файлом).


4
Мені довелося деякий час працювати зі зламаною оболонкою, яка не видалила останній новий рядок із заміни команди (це не підміна процесу ), і вона порушила майже все. Наприклад, якщо ви це зробили pwd=`pwd`; ls $pwd/$file, ви отримали новий рядок перед /, і додавання імені у подвійні лапки не допомогло. Це було швидко виправлено. Це було ще в 1983-5 часових межах на ICL Perq PNX; оболонка не мала $PWDвбудованої змінної.
Джонатан Леффлер

19

Якщо вас цікавлять конкретні рядки, використовуйте масив результатів:

declare RESULT=($(./myscript))  # (..) = array
echo "First line: ${RESULT[0]}"
echo "Second line: ${RESULT[1]}"
echo "N-th line: ${RESULT[N]}"

3
Якщо в рядках є пробіли, це буде рахувати поля (вміст між пробілами), а не рядки.
Ліам

2
Ви могли б використовувати readarrayі процес заміни замість підстановки команди: readarray -t RESULT < <(./myscript>.
чепнер

15

На додаток до відповіді, отриманої від @ l0b0, у мене просто була ситуація, коли мені потрібно було тримати будь-який зворотний рядок нових рядків за сценарієм та перевіряти код повернення сценарію. І проблема з відповіддю l0b0 полягає в тому, що 'echo x' було скинуто $? назад до нуля ... тому мені вдалося придумати це дуже хитро рішення:

RESULTX="$(./myscript; echo x$?)"
RETURNCODE=${RESULTX##*x}
RESULT="${RESULTX%x*}"

Це прекрасно. Насправді у мене був випадок використання, коли я хочу мати die ()функцію, яка приймає довільний код виходу та необов'язково повідомлення, і я хотів використати іншу usage ()функцію для подачі повідомлення, але нові рядки все більше стискаються, сподіваюся, це дозволяє мені обійти це.
dragon788

1

Спробувавши більшість рішень тут, найлегше, що я знайшов, було очевидне - використання тимчасового файлу. Я не впевнений, що ви хочете зробити зі своїм кількома рядковими висновками, але ви можете потім вирішувати це по черзі за допомогою функції read. Єдине, що ви насправді не можете зробити - це легко дотримати це все в одній змінній, але для більшості практичних цілей це легше вирішити.

./myscript.sh > /tmp/foo
while read line ; do 
    echo 'whatever you want to do with $line'
done < /tmp/foo

Швидкий злом, щоб змусити виконати запитувані дії:

result=""
./myscript.sh > /tmp/foo
while read line ; do
  result="$result$line\n"
done < /tmp/foo
echo -e $result

Зауважте, це додає додатковий рядок. Якщо ви працюєте над ним, ви можете кодувати його, я просто лінивий.


EDIT: Хоча цей випадок працює чудово, люди, які читають це, повинні усвідомлювати, що ви можете легко розчавити stdin всередині циклу while, тим самим даючи вам сценарій, який буде виконувати один рядок, очищати stdin та виходити. Як ssh зробить це я думаю? Нещодавно я побачив це, інші приклади коду тут: /unix/24260/reading-lines-from-a-file-with-bash-for-vs-time

Ще раз! Цього разу з іншим файловим файлом (stdin, stdout, stderr 0-2, тому ми можемо використовувати & 3 або вище в bash).

result=""
./test>/tmp/foo
while read line  <&3; do
    result="$result$line\n"
done 3</tmp/foo
echo -e $result

Ви також можете використовувати mktemp, але це лише швидкий приклад коду. Використання mktemp виглядає так:

filenamevar=`mktemp /tmp/tempXXXXXX`
./test > $filenamevar

Потім використовуйте $ filenamevar, як і власне ім'я файлу. Напевно, тут не потрібно пояснювати, але хтось скаржився у коментарях.


Я також спробував інші рішення, з вашої першої пропозиції я нарешті змусив свій сценарій працювати
Kar.ma

1
Downvote: Це надмірно складно і не дозволяє уникнути безлічі загальних bashпідводних каменів .
трійка

Я хтось розповідав мені про дивну проблему з файдовою обробкою stdin днями, і я був схожий на "вау". Лемма додає щось дійсно швидко.
користувач1279741

У Bash ви можете використовувати readрозширення команди, -u 3щоб прочитати з дескриптора файлів 3.
Джонатан Леффлер

Чому б поки не ... зробити ... зроблено <<(.
Myscript.sh

0

Як щодо цього, він буде читати кожен рядок до змінної, який можна використовувати згодом! скажімо, вихідний скрипт переспрямований у файл, який називається myscript_output

awk '{while ( (getline var < "myscript_output") >0){print var;} close ("myscript_output");}'

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