Как сохранить стандартную ошибку в переменной - PullRequest
154 голосов
/ 07 июня 2009

Допустим, у меня есть скрипт, подобный следующему:

useless.sh

echo "This Is Error" 1>&2
echo "This Is Output" 

И у меня есть другой сценарий оболочки:

alsoUseless.sh

./useless.sh | sed 's/Output/Useless/'

Я хочу записать "This Is Error" или любой другой stderr из useless.sh в переменную. Давайте назовем это ОШИБКА.

Обратите внимание, что я использую стандартный вывод для чего-то. Я хочу продолжать использовать stdout, поэтому перенаправление stderr в stdout в этом случае бесполезно.

Итак, в основном, я хочу сделать

./useless.sh 2> $ERROR | ...

но это, очевидно, не работает.

Я также знаю, что могу сделать

./useless.sh 2> /tmp/Error
ERROR=`cat /tmp/Error`

но это безобразно и не нужно.

К сожалению, если здесь не появятся ответы, это то, что я собираюсь сделать.

Я надеюсь, что есть другой способ.

У кого-нибудь есть идеи получше?

Ответы [ 15 ]

75 голосов
/ 07 июня 2009

Было бы лучше записать файл ошибок следующим образом:

ERROR=$(</tmp/Error)

Оболочка распознает это и не должна запускать 'cat' для получения данных.

Большой вопрос сложен. Я не думаю, что есть простой способ сделать это. Вам придется встроить весь конвейер в под-оболочку, в конечном итоге отправив окончательный стандартный вывод в файл, чтобы вы могли перенаправить ошибки на стандартный вывод.

ERROR=$( { ./useless.sh | sed s/Output/Useless/ > outfile; } 2>&1 )

Обратите внимание, что точка с запятой необходима (в классических оболочках - Bourne, Korn - наверняка; вероятно, в Bash тоже). '{}' выполняет перенаправление ввода / вывода поверх вложенных команд. Как уже было написано, он будет фиксировать ошибки и из sed.

ПРЕДУПРЕЖДЕНИЕ: Формально непроверенный код - используйте на свой страх и риск.

63 голосов
/ 08 июня 2009

alsoUseless.sh

Это позволит вам направить вывод вашего скрипта useless.sh с помощью такой команды, как sed, и сохранить stderr в переменной с именем error. Результат канала отправляется на stdout для отображения или для передачи в другую команду.

Он устанавливает пару дополнительных файловых дескрипторов для управления перенаправлениями, необходимыми для этого.

#!/bin/bash

exec 3>&1 4>&2 #set up extra file descriptors

error=$( { ./useless.sh | sed 's/Output/Useless/' 2>&4 1>&3; } 2>&1 )

echo "The message is \"${error}.\""

exec 3>&- 4>&- # release the extra file descriptors
52 голосов
/ 07 июня 2009

Перенаправленный stderr на стандартный вывод, стандартный вывод на / dev / null, а затем с помощью обратных клавиш или $() захватывает перенаправленный стандартный вывод:

ERROR=$(./useless.sh 2>&1 >/dev/null)
7 голосов
/ 11 февраля 2018

Существует множество дубликатов для этого вопроса, многие из которых имеют несколько более простой сценарий использования, при котором вы не хотите захватывать stderr и stdout и , код завершения всех в то же время.

if result=$(useless.sh 2>&1); then
    stdout=$result
else
    rc=$?
    stderr=$result
fi

работает для распространенного сценария, когда вы ожидаете либо правильного вывода в случае успеха, либо диагностического сообщения на stderr в случае сбоя.

Обратите внимание, что управляющие операторы оболочки уже проверяют $? под капотом; так что все, что выглядит как

cmd
if [ $? -eq 0 ], then ...

это просто неуклюжий, недиоматичный способ сказать

if cmd; then ...
5 голосов
/ 28 ноября 2013
# command receives its input from stdin.
# command sends its output to stdout.
exec 3>&1
stderr="$(command </dev/stdin 2>&1 1>&3)"
exitcode="${?}"
echo "STDERR: $stderr"
exit ${exitcode}
3 голосов
/ 26 июля 2012

Вот как я это сделал:

#
# $1 - name of the (global) variable where the contents of stderr will be stored
# $2 - command to be executed
#
captureStderr()
{
    local tmpFile=$(mktemp)

    $2 2> $tmpFile

    eval "$1=$(< $tmpFile)"

    rm $tmpFile
}

Пример использования:

captureStderr err "./useless.sh"

echo -$err-

Он использует временный файл. Но, по крайней мере, уродливые вещи заключены в функцию.

2 голосов
/ 08 июня 2009

Это интересная проблема, на которую я надеялся найти элегантное решение. К сожалению, я получаю решение, похожее на мистер Леффлер, но добавлю, что вы можете бесполезно вызывать функцию Bash для улучшения читабельности:

#!/bin/bash

function useless {
    /tmp/useless.sh | sed 's/Output/Useless/'
}

ERROR=$(useless)
echo $ERROR

Все другие виды перенаправления вывода должны быть поддержаны временным файлом.

1 голос
/ 22 мая 2018

Захват и печать stderr

ERROR=$( ./useless.sh 3>&1 1>&2 2>&3 | tee /dev/fd/2 )

Разбивка

Вы можете использовать $() для захвата stdout, но вместо этого вы хотите захватить stderr. Таким образом, вы меняете stdout и stderr. Использование fd 3 в качестве временного хранилища в стандартном алгоритме подкачки.

Если вы хотите захватить И распечатать, используйте tee, чтобы сделать копию. В этом случае вывод tee будет записан $() вместо того, чтобы идти на консоль, но stderr (tee) все равно будет идти на консоль, поэтому мы используем его в качестве второго вывода для tee через специальный файл /dev/fd/2, поскольку tee ожидает путь к файлу, а не номер fd.

ПРИМЕЧАНИЕ. Это очень много перенаправлений в одной строке, и порядок имеет значение. $() захватывает стандартный вывод tee в конце конвейера, и сам конвейер направляет стандартный вывод ./useless.sh на стандартный tee ПОСЛЕ того, как мы меняли стандартные stdin и стандартный вывод на ./useless.sh.

Использование стандартного вывода ./useless.sh

ОП сказал, что он все еще хотел использовать (а не только печатать) стандартный вывод, например ./useless.sh | sed 's/Output/Useless/'.

Нет проблем, просто сделайте это ПЕРЕД обменом stdout и stderr. Я рекомендую переместить его в функцию или файл (также-useless.sh) и вызвать его вместо ./useless.sh в строке выше.

Однако, если вы хотите CAPTURE stdout AND stderr, то я думаю, что вам придется использовать временные файлы, потому что $() будет делать только один файл за раз и создает подоболочку, из которой вы не сможете вернуть переменные. *

1 голос
/ 19 августа 2015

Этот пост помог мне придумать подобное решение для моих собственных целей:

MESSAGE=`{ echo $ERROR_MESSAGE | format_logs.py --level=ERROR; } 2>&1`

Тогда, пока наше СООБЩЕНИЕ не является пустой строкой, мы передаем ее другим материалам. Это даст нам знать, если наш format_logs.py потерпел неудачу с каким-то исключением из Python.

1 голос
/ 25 октября 2013
$ b=$( ( a=$( (echo stdout;echo stderr >&2) ) ) 2>&1 )
$ echo "a=>$a b=>$b"
a=>stdout b=>stderr
...