Захват многострочного вывода в переменную 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");}'