Как сохранить стандартную ошибку в переменной в скрипте Bash

предположим, у меня есть такой сценарий:

useless.sh

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

и у меня есть другой скрипт:

alsoUseless.sh

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

Я хочу захватить "это ошибка" или любой другой stderr из useless.sh, в переменную. Назовем это ошибкой.

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

так, в принципе, я хочу сделать

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

но это явно не работает.

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

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

но это некрасиво и ненужно.

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

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

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

14 ответов


было бы аккуратнее захватить файл ошибки таким образом:

ERROR=$(</tmp/Error)

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

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

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

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

(формально непроверенный код-использовать на свой страх и риск.)


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

перенаправлено stderr в stdout, stdout в /dev / null, а затем используйте backticks или $() для захвата перенаправленного stderr:

ERROR=$(./useless.sh 2>&1 >/dev/null)

есть много дубликатов для этого вопроса, многие из которых имеют немного более простой сценарий использования, где вы не хотите захватывать 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 ...

# 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}

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

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

     2> $tmpFile

    eval "=$(< $tmpFile)"

    rm $tmpFile
}

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

captureStderr err "./useless.sh"

echo -$err-

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


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

#!/bin/bash

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

ERROR=$(useless)
echo $ERROR

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


$ b=$( ( a=$( (echo stdout;echo stderr >&2) ) ) 2>&1 )
$ echo "a=>$a b=>$b"
a=>stdout b=>stderr

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

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

тогда, пока наше сообщение не является пустой строкой, мы передаем его другим вещам. Это даст нам знать, если наши format_logs.py не удалось с каким-то исключением python.


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

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

распад

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

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

Примечание: это очень много перенаправлений в одной строке, и порядок имеет значение. $() захватывает stdout tee в конце трубопровода и самого трубопровода маршруты stdout ./useless.sh к stdin tee после того, как мы поменяли stdin и stdout на ./useless.sh.

использование stdout of ./useless.sh

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

нет проблем, просто сделайте это перед заменой stdout и stderr. Я рекомендую переместить его в функцию или файл (also-useless.sh) и называя это вместо ./useless.sh в верхней строке.

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


если вы хотите обойти использование временного файла, вы можете использовать замену процесса. Я еще не до конца разобрался. Это была моя первая попытка:--8-->

$ .useless.sh 2> >( ERROR=$(<) )
-bash: command substitution: line 42: syntax error near unexpected token `)'
-bash: command substitution: line 42: `<)'

тогда я попробовал

$ ./useless.sh 2> >( ERROR=$( cat <() )  )
This Is Output
$ echo $ERROR   # $ERROR is empty
$ ./useless.sh 2> >( cat <() > asdf.txt )
This Is Output
$ cat asdf.txt
This Is Error

таким образом, замена процесса делает вообще правильно... к сожалению, всякий раз, когда я обертываю STDIN внутри >( ) С $() в попытке захватить это в переменную, я теряю содержание $(). Я думаю, что это потому, что $() запускает подпроцесс, который больше не имеет доступа к файловому дескриптору в /dev/fd, принадлежащему родительскому процессу.

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


в zsh:

{ . ./useless.sh > /dev/tty } 2>&1 | read ERROR
$ echo $ERROR
( your message )

POSIX

STDERR можно захватить с помощью магии перенаправления:

$ { error=$( { { ls -ld /XXXX /bin | tr o Z ; } 1>&3 ; } 2>&1); } 3>&1
lrwxrwxrwx 1 rZZt rZZt 7 Aug 22 15:44 /bin -> usr/bin/

$ echo $error
ls: cannot access '/XXXX': No such file or directory

обратите внимание, что трубопровод STDOUT команды (здесь ls) делается внутри самого внутреннего { }. Если вы выполняете простую команду (например, не трубу), вы можете удалить эти внутренние фигурные скобки.

вы не можете трубить вне команды, так как трубопровод делает подрешетку в bash и zsh, и назначение переменной в подрешетке не будет доступно для текущая оболочка.

Баш

на bash, было бы лучше не предполагать, что файловый дескриптор 3 не используется:

{ error=$( { { ls -ld /XXXX /bin | tr o Z ; } 1>&$tmp ; } 2>&1); } {tmp}>&1; 
exec {tmp}>&-  # With this syntax the FD stays open

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


спасибо ответ для общей идеи.


на ошибок команды:

execute [INVOKING-FUNCTION] [COMMAND]

execute () {
    error=$( 2>&1 >/dev/null)

    if [ $? -ne 0 ]; then
        echo ": $error"
        exit 1
    fi
}

вдохновение в бережливом производстве: