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