Как сохранить стандартную ошибку в переменной
Скажем, у меня есть script, как показано ниже:
useless.sh
echo "This Is Error" 1>&2
echo "This Is Output"
И у меня есть еще одна оболочка script:
alsoUseless.sh
./useless.sh | sed 's/Output/Useless/'
Я хочу записать "This Is Error" или любой другой stderr из useless.sh в переменную.
Позвольте называть его ОШИБКОЙ.
Обратите внимание, что я использую stdout для чего-то. Я хочу продолжить использование stdout, поэтому перенаправление stderr в stdout не является полезным в этом случае.
Итак, в основном, я хочу сделать
./useless.sh 2> $ERROR | ...
но это, очевидно, не работает.
Я также знаю, что я мог сделать
./useless.sh 2> /tmp/Error
ERROR=`cat /tmp/Error`
но это уродливое и ненужное.
К сожалению, если здесь нет ответов, что мне придется делать.
Я надеюсь на другой путь.
У кого-нибудь есть лучшие идеи?
Ответы
Ответ 1
Было бы лучше записать файл ошибок так:
ERROR=$(</tmp/Error)
Оболочка распознает это и не должна запускать ' cat
' для получения данных.
Большой вопрос сложен. Я не думаю, что есть простой способ сделать это. Вам нужно будет встроить весь конвейер в под-оболочку, в конечном итоге отправив окончательный стандартный вывод в файл, чтобы вы могли перенаправить ошибки в стандартный вывод.
ERROR=$( { ./useless.sh | sed s/Output/Useless/ > outfile; } 2>&1 )
Обратите внимание, что точка с запятой необходима (в классических оболочках - Bourne, Korn - наверняка; вероятно, в Bash тоже). ' {}
' Выполняет перенаправление ввода/вывода поверх вложенных команд. Как написано, он также будет фиксировать ошибки от sed
.
ВНИМАНИЕ: Формально непроверенный код - используйте на свой страх и риск.
Ответ 2
alsoUseless.sh
Это позволит вам вывести вывод вашего useless.sh
script с помощью команды типа 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
Ответ 3
Перенаправлено stderr в stdout, stdout в /dev/null, а затем использовать обратные выходы или $()
для захвата перенаправленного stderr:
ERROR=$(./useless.sh 2>&1 >/dev/null)
Ответ 4
Для этого вопроса много дубликатов, многие из которых имеют несколько более простой сценарий использования, когда вы не хотите одновременно записывать 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
# 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}
Ответ 6
Вот как я это сделал:
#
# $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-
Он использует временный файл. Но, по крайней мере, уродливые вещи завернуты в функцию.
Ответ 7
Это интересная проблема, на которую я надеялся, что есть изящное решение. К сожалению, я получаю решение, подобное г-ну Леффлеру, но я добавлю, что вы можете бесполезно использовать функцию Bash для улучшения удобочитаемости:
#!/bin/bash
function useless {
/tmp/useless.sh | sed 's/Output/Useless/'
}
ERROR=$(useless)
echo $ERROR
Все другие виды перенаправления вывода должны поддерживаться временным файлом.
Ответ 8
Для удобства читателя этот рецепт здесь
- может быть повторно использован как oneliner для захвата stderr в переменную
- по-прежнему дает доступ к коду возврата команды
- Жертва временного файлового дескриптора 3 (который вы, конечно, можете изменить)
- И не выставляет этот временный файловый дескриптор внутренней команде
Если вы хотите перехватить stderr
какой-либо command
в var
вы можете сделать
{ var="$( { command; } 2>&1 1>&3 3>&- )"; } 3>&1;
После этого у вас есть все:
echo "command gives $? and stderr '$var'";
Если command
простая (а не что-то вроде a | b
), вы можете оставить внутреннее {}
стороне:
{ var="$(command 2>&1 1>&3 3>&-)"; } 3>&1;
Обернутый в простой многоразовый bash
-function (вероятно, требуется версия 3 и выше для local -n
):
: catch-stderr var cmd [args..]
catch-stderr() { local -n v="$1"; shift && { v="$("[email protected]" 2>&1 1>&3 3>&-)"; } 3>&1; }
Разъяснение:
-
local -n
псевдонимы local -n
"$ 1" (это переменная для catch-stderr
) -
3>&1
использует файловый дескриптор 3 для сохранения там стандартных точек -
{ command; }
{ command; }
(или "$ @") затем выполняет команду в выходных данных захвата $(..)
- Обратите внимание, что здесь важен точный порядок (неправильный способ неправильно перемешивает дескрипторы файлов):
-
2>&1
перенаправляет stderr
на вывод, захватывая $(..)
-
1>&3
перенаправляет стандартный stdout
с вывода, захватывающего $(..)
на "внешний" stdout
который был сохранен в файловом дескрипторе 3. Обратите внимание, что stderr
прежнему ссылается на то место, на которое указывал FD 1: на вывод захвата $(..)
-
3>&-
затем закрывает файловый дескриптор 3, так как он больше не нужен, так что command
внезапно не обнаруживает какой-то неизвестный открытый файловый дескриптор. Обратите внимание, что внешняя оболочка все еще имеет открытый FD 3, но command
ее не увидит. - Последнее важно, потому что некоторые программы, такие как
lvm
жалуются на неожиданные файловые дескрипторы. И lvm
жалуется на stderr
- именно то, что мы собираемся захватить!
Вы можете поймать любой другой дескриптор файла с этим рецептом, если вы адаптируетесь соответственно. Конечно, кроме файлового дескриптора 1 (здесь логика перенаправления будет неправильной, но для файлового дескриптора 1 вы можете просто использовать var=$(command)
как обычно).
Обратите внимание, что это жертвует файловым дескриптором 3. Если вам понадобится этот файловый дескриптор, смело меняйте номер. Но учтите, что некоторые оболочки (начиная с 1980-х годов) могут понимать 99>&1
как аргумент 9
за которым следуют 9>&1
(это не проблема для bash
).
Также обратите внимание, что не так легко сделать этот FD 3 настраиваемым через переменную. Это делает вещи очень нечитаемыми:
: catch-var-from-fd-by-fd variable fd-to-catch fd-to-sacrifice command [args..]
catch-var-from-fd-by-fd()
{
local -n v="$1";
local fd1="$2" fd2="$3";
shift 3 || return;
eval exec "$fd2>&1";
v="$(eval '"[email protected]"' "$fd1>&1" "1>&$fd2" "$fd2>&-")";
eval exec "$fd2>&-";
}
Примечание по безопасности: Первые 3 аргумента для catch-var-from-fd-by-fd
не должны приниматься сторонними разработчиками. Всегда дайте их явно "статичным" способом.
Так что никакая команда -n o -n o catch-var-from-fd-by-fd $var $fda $fdb $command
никогда не делает этого!
Если вам случится передать имя переменной переменной, по крайней мере сделайте это следующим образом: local -n var="$var"; catch-var-from-fd-by-fd var 3 5 $command
local -n var="$var"; catch-var-from-fd-by-fd var 3 5 $command
Это по-прежнему не защитит вас от каждого эксплойта, но, по крайней мере, поможет обнаружить и избежать распространенных ошибок сценариев.
Заметки:
-
catch-var-from-fd-by-fd var 2 3 cmd..
совпадает с catch-stderr var cmd..
-
shift || return
shift || return
- это просто способ предотвратить неприятные ошибки, если вы забудете указать правильное количество аргументов. Возможно, завершение оболочки будет другим способом (но это затрудняет тестирование из командной строки). - Рутина была написана так, чтобы ее было легче понять. Можно переписать функцию так, чтобы она не нуждалась в
exec
, но тогда она становится действительно уродливой. - Эта процедура также может быть переписана для non-
bash
так что нет необходимости в local -n
. Однако тогда вы не можете использовать локальные переменные, и это становится ужасно! - Также обратите внимание, что
eval
используются безопасным образом. Обычно eval
считается опасным. Однако в этом случае это не более зло, чем использование "[email protected]"
(для выполнения произвольных команд). Однако, пожалуйста, не забудьте использовать точное и правильное цитирование, как показано здесь (иначе это становится очень и очень опасным).
Ответ 9
$ b=$( ( a=$( (echo stdout;echo stderr >&2) ) ) 2>&1 )
$ echo "a=>$a b=>$b"
a=>stdout b=>stderr
Ответ 10
Этот пост помог мне придумать аналогичное решение для моих собственных целей:
MESSAGE=`{ echo $ERROR_MESSAGE | format_logs.py --level=ERROR; } 2>&1`
Тогда, пока наше СООБЩЕНИЕ не является пустой строкой, мы передаем ее другим вещам. Это сообщит нам, если наш format_logs.py не удалось с каким-то исключением python.
Ответ 11
Capture AND Print 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.
ПРИМЕЧАНИЕ. Это очень много перенаправлений в одной строке, и порядок имеет значение. $()
захватывает stdout tee
в конце конвейера, и сам трубопровод направляет stdout ./useless.sh
на stdin tee
мы заменили stdin и stdout для ./useless.sh
.
Использование stdout./useless.sh
OP сказал, что он все еще хотел использовать (не только печатать) stdout, например ./useless.sh | sed 's/Output/Useless/'
./useless.sh | sed 's/Output/Useless/'
.
Нет проблем, просто сделайте это ПЕРЕД заменой stdout и stderr. Я рекомендую переместить его в функцию или файл (also-useless.sh) и вызвать это вместо. /useless.sh в строке выше.
Однако, если вы хотите CAPTURE stdout AND stderr, тогда я думаю, что вы должны отступать от временных файлов, потому что $()
будет делать только одно за раз, и это делает подоболочку, из которой вы не можете возвращать переменные.
Ответ 12
Простое решение
{ ERROR=$(./useless.sh 2>&1 1>&$out); } {out}>&1
echo "-"
echo $ERROR
Будет производить:
This Is Output
-
This Is Error
Ответ 13
Если вы хотите обойти использование временного файла, вы можете использовать замещение процесса. Я еще не успел это сделать. Это была моя первая попытка:
$ .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, к сожалению, я, похоже, не могу манипулировать им так, как хочу.
Ответ 14
В zsh:
{ . ./useless.sh > /dev/tty } 2>&1 | read ERROR
$ echo $ERROR
( your message )
Ответ 15
Для проверки ошибок ваших команд:
execute [INVOKING-FUNCTION] [COMMAND]
execute () {
function="${1}"
command="${2}"
error=$(eval "${command}" 2>&1 >"/dev/null")
if [ ${?} -ne 0 ]; then
echo "${function}: ${error}"
exit 1
fi
}
Вдохновленный в Бережливом производстве:
Ответ 16
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
.
Благодаря этому ответу на общую идею.