Как сохранить стандартную ошибку в переменной в скрипте 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
}
вдохновение в бережливом производстве: