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

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

abc
def
ghi

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

declare RESULT=$(./myscript)

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

abc def ghi

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

6 ответов

Решение

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

echo "$RESULT"

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

echo $RESULT

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

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

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

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

В случае, если вас интересуют конкретные строки, используйте массив результатов:

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

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

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

Разбор множественного вывода

Введение

Так что ваши myscript выведите 3 строки, может выглядеть так:

myscript() { echo $'abc\ndef\nghi'; }

или же

myscript() { local i; for i in abc def ghi ;do echo $i; done ;}

Хорошо, это функция, а не скрипт (путь не нужен ./), но вывод такой же

myscript
abc
def
ghi

Учитывая код результата

Чтобы проверить код результата, функция test будет выглядеть так:

myscript() { local i;for i in abc def ghi ;do echo $i;done;return $((RANDOM%128));}

1. Сохранение нескольких выходных данных в одной переменной с отображением новых строк

Ваша операция верна:

RESULT=$(myscript)

О коде результата вы можете добавить:

RCODE=$?

даже в той же строке:

RESULT=$(myscript) RCODE=$?

потом

echo $RESULT 
abc def ghi

echo "$RESULT"
abc
def
ghi

echo ${RESULT@Q}
$'abc\ndef\nghi'

printf "%q\n" "$RESULT"
$'abc\ndef\nghi'

но для отображения определения переменной используйте declare -p:

declare -p RESULT
declare -- RESULT="abc
def
ghi"

2. Анализ нескольких выходных данных в массиве с использованием mapfile

Сохранение ответа в myvar переменная:

mapfile -t myvar < <(myscript)
echo ${myvar[2]}
ghi

Отображение $myvar:

declare -p myvar
declare -a myvar=([0]="abc" [1]="def" [2]="ghi")

Учитывая код результата

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

RESULT=$(myscript) RCODE=$?
mapfile -t myvar <<<"$RESULT"

3. Анализ нескольких выходных данных по порядку. read в группе команд

{ read firstline; read secondline; read thirdline;} < <(myscript)
echo $secondline
def

Отображение переменных:

declare -p firstline secondline thirdline
declare -- firstline="abc"
declare -- secondline="def"
declare -- thirdline="ghi"

Я часто использую:

{ read foo;read foo total use free foo ;} < <(df -k /)

потом

declare -p use free total
declare -- use="843476"
declare -- free="582128"
declare -- total="1515376"

Учитывая код результата

Тот же предварительный шаг:

RESULT=$(myscript) RCODE=$?
{ read firstline; read secondline; read thirdline;} <<<"$RESULT"

declare -p firstline secondline thirdline RCODE
declare -- firstline="abc"
declare -- secondline="def"
declare -- thirdline="ghi"
declare -- RCODE="50"

Попробовав большинство решений здесь, я обнаружил, что проще всего было использовать временный файл. Я не уверен, что вы хотите сделать со своим многострочным выводом, но вы можете справиться с этим построчно, используя 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 сделает то, что я думаю? Я только недавно видел это, другие примеры кода здесь: https://unix.stackexchange.com/questions/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 так же, как действительное имя файла. Вероятно, не нужно объяснять здесь, но кто-то жаловался в комментариях.

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

awk '{while ( (getline var < "myscript_output") >0){print var;} close ("myscript_output");}'
Другие вопросы по тегам