Захват многострочного вывода в переменную Bash

583

У меня есть скрипт «myscript», который выводит следующее:

abc
def
ghi

в другом сценарии я звоню:

declare RESULT=$(./myscript)

и $RESULTполучает значение

abc def ghi

Есть ли способ сохранить результат либо с символами новой строки, либо с символом '\ n', чтобы я мог вывести его с помощью ' echo -e'?

Parker
источник
1
это удивляет меня у вас нет $ (cat ./myscipt)? иначе я бы ожидал, что он попытается выполнить команды abc, def и ghi
Йоханнес Шауб - litb
@litb: да, я так думаю; Вы также можете использовать $ (<./ myscript), который избегает выполнения команды.
Джонатан Леффлер
2
(Примечание: два комментария выше относятся к пересмотру вопроса, который начался. У меня есть скрипт «myscript», который содержит следующее , что привело к вопросам. Текущая редакция вопроса ( у меня есть скрипт » myscript ', который выводит следующее ) делает комментарии излишними. Однако, пересмотр 2011-11-11, намного позже, чем два комментария были сделаны
Джонатан Леффлер

Ответы:

1083

На самом деле, RESULT содержит то, что вы хотите - продемонстрировать:

echo "$RESULT"

То, что вы показываете, это то, что вы получаете от:

echo $RESULT

Как отмечено в комментариях, разница состоит в том, что (1) версия переменной () в двойных кавычках echo "$RESULT"сохраняет внутренний интервал значения в точности так, как он представлен в переменной - новые строки, табуляции, несколько пробелов и все - в то время как (2 ) версия без кавычек ( echo $RESULT) заменяет каждую последовательность из одного или нескольких пробелов, табуляции и новых строк одним пробелом. Таким образом, (1) сохраняет форму входной переменной, тогда как (2) создает потенциально очень длинную единственную строку вывода с «словами», разделенными пробелами (где «слово» - это последовательность непробельных символов; Никаких буквенно-цифровых символов в любом из слов).

Джонатан Леффлер
источник
69
@troelskn: разница в том, что (1) версия переменной в двойных кавычках сохраняет внутренний интервал значения точно так, как он представлен в переменной, символах новой строки, табуляции, нескольких пробелах и т. д., тогда как (2) версия без кавычек заменяет каждая последовательность из одного или нескольких пробелов, табуляции и новых строк с одним пробелом. Таким образом, (1) сохраняет форму входной переменной, тогда как (2) создает потенциально очень длинную единственную строку вывода с «словами», разделенными пробелами (где «слово» - это последовательность непробельных символов; Никаких буквенно-цифровых символов в любом из слов).
Джонатан Леффлер
23
Чтобы облегчить понимание ответа: ответ говорит, что echo "$ RESULT" сохраняет символ новой строки, а echo $ RESULT - нет.
Ю Шен
Это не в состоянии сохранить переводы строки и пробелы в некоторых ситуациях.
CommaToast
3
@CommaToast: Вы собираетесь уточнить это? Конечные переводы строки теряются; нет простого способа обойти это. Ведущие пробелы - я не знаю ни о каких обстоятельствах, при которых они потерялись.
Джонатан Леффлер
90

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

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

Это особенно важно, если вы хотите обработать все возможные имена файлов (чтобы избежать неопределенного поведения, например, работы с неправильным файлом).

l0b0
источник
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]}"
user2574210
источник
3
Если в строках есть пробелы, это будет считать поля (содержимое между пробелами), а не строки.
Лиам
2
Вы могли бы использовать readarrayи процесс замены вместо подстановки команды: readarray -t RESULT < <(./myscript>.
chepner
15

В дополнение к ответу, данному @ l0b0, у меня просто была ситуация, когда мне нужно было сохранить любые завершающие строки, выводимые скриптом, и проверить код возврата скрипта. И проблема с ответом l0b0 состоит в том, что 'echo x' сбрасывал $? вернуться к нулю ... так что мне удалось придумать это очень хитрое решение:

RESULTX="$(./myscript; echo x$?)"
RETURNCODE=${RESULTX##*x}
RESULT="${RESULTX%x*}"
Lurchman
источник
Это прекрасно, у меня действительно был случай использования, где я хочу иметь 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

Обратите внимание, что это добавляет дополнительную строку. Если вы работаете над этим, вы можете кодировать его, я просто слишком ленив.


РЕДАКТИРОВАТЬ: Хотя этот случай работает очень хорошо, люди, читающие это, должны знать, что вы можете легко сжать свой стандартный ввод в цикле while, что даст вам скрипт, который будет запускать одну строку, очищать стандартный ввод и выходить. Как ssh сделает то, что я думаю? Я недавно видел это, другие примеры кода здесь: /unix/24260/reading-lines-from-a-file-with-bash-for-vs- while

Еще один раз! На этот раз с другим дескриптором файла (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 так же, как действительное имя файла. Вероятно, не нужно объяснять здесь, но кто-то жаловался в комментариях.

user1279741
источник
Я пробовал и другие решения, с вашим первым предложением я наконец-то получил свой скрипт
Kar.ma
1
Даунвот: Это слишком сложно и не позволяет избежать множества распространенных bashошибок .
tripleee
Однажды кто-то рассказал мне о странной проблеме с файловыми дескрипторами stdin, и я сказал «вау». Позвольте мне добавить что-то действительно быстро.
user1279741
В Bash вы можете использовать readрасширение команды -u 3для чтения из файлового дескриптора 3.
Джонатан Леффлер
Почему бы не ... сделать ... сделано <<(. Myscript.sh)
jtgd
0

Как насчет этого, он будет читать каждую строку переменной, и это может быть использовано впоследствии! скажем, вывод myscript перенаправляется в файл с именем myscript_output

awk '{while ( (getline var < "myscript_output") >0){print var;} close ("myscript_output");}'
Рахул Редди
источник
4
Ну, это не Баш, это ужасно.
vadipp