Захват и stdout и stderr в Bash
Я знаю этот синтаксис
var=`myscript.sh`
или
var=$(myscript.sh)
Захват результата (stdout
) myscript.sh
в var
. Я мог бы перенаправить stderr
в stdout
, если бы захотел захватить оба. Как сохранить каждую из них для разделения переменных?
Мой вариант использования здесь, если код возврата отличен от нуля, я хочу откликнуть stderr
и подавить в противном случае. Могут быть другие способы сделать это, но этот подход кажется, что он будет работать, если это действительно возможно.
Ответы
Ответ 1
Невозможно записать оба без временного файла.
Вы можете записать stderr в переменную и передать stdout на экран пользователя (образец из здесь):
exec 3>&1 # Save the place that stdout (1) points to.
output=$(command 2>&1 1>&3) # Run command. stderr is captured.
exec 3>&- # Close FD #3.
# Or this alternative, which captures stderr, letting stdout through:
{ output=$(command 2>&1 1>&3-) ;} 3>&1
Но невозможно зафиксировать как stdout, так и stderr:
То, что вы не можете сделать, это захват stdout в одной переменной и stderr в другой, используя только перенаправления FD. Вы должны использовать временный файл (или именованный канал) для достижения этого.
Ответ 2
Там действительно уродливый способ захватить stderr
и stdout
в двух отдельных переменных без временных файлов (если вам нравится сантехника), используя замещение процесса, source
и declare
соответственно. Я назову вашу команду banana
. Вы можете имитировать такую команду с помощью функции:
banana() {
echo "banana to stdout"
echo >&2 "banana to stderr"
}
Предполагаю, что вы хотите стандартный вывод banana
в переменной bout
и стандартную ошибку banana
в переменной berr
. Здесь волшебство, которое достигнет этого (Bash ≥4 только):
. <({ berr=$({ bout=$(banana); } 2>&1; declare -p bout >&2); declare -p berr; } 2>&1)
Итак, что здесь происходит?
Пусть начнется с самого внутреннего термина:
bout=$(banana)
Это стандартный способ присвоить bout
стандартный вывод banana
, стандартная ошибка отображается на вашем терминале.
Тогда:
{ bout=$(banana); } 2>&1
по-прежнему будет назначать bout
stdout banana
, но stderr banana
отображается на терминале через stdout (благодаря перенаправлению 2>&1
.
Тогда:
{ bout=$(banana); } 2>&1; declare -p bout >&2
будет делать, как указано выше, но также будет отображать на терминале (через stderr) содержимое bout
с помощью встроенного declare
: это будет повторно использовано в ближайшее время.
Тогда:
berr=$({ bout=$(banana); } 2>&1; declare -p bout >&2); declare -p berr
назначит berr
stderr banana
и отобразит содержимое berr
с помощью declare
.
В этот момент вы будете иметь на экране терминала:
declare -- bout="banana to stdout"
declare -- berr="banana to stderr"
с линией
declare -- bout="banana to stdout"
отображается через stderr.
Окончательное перенаправление:
{ berr=$({ bout=$(banana); } 2>&1; declare -p bout >&2); declare -p berr; } 2>&1
будет отображаться предыдущее через stdout.
Наконец, мы используем замещение процесса для источника содержимого этих строк.
Вы также указали код возврата команды. Измените banana
на:
banana() {
echo "banana to stdout"
echo >&2 "banana to stderr"
return 42
}
Мы также будем иметь код возврата banana
в переменной bret
так:
. <({ berr=$({ bout=$(banana); bret=$?; } 2>&1; declare -p bout bret >&2); declare -p berr; } 2>&1)
Вы можете обойтись без источников и подстановки процесса, используя также eval
(и он работает с Bash < 4 тоже):
eval "$({ berr=$({ bout=$(banana); bret=$?; } 2>&1; declare -p bout bret >&2); declare -p berr; } 2>&1)"
И все это безопасно, потому что только те, что мы source
ing или eval
ing, получены из declare -p
и всегда будут правильно экранированы.
Конечно, если вы хотите вывод в массиве (например, с mapfile
, если вы используете Bash ≥4-иначе замените mapfile
на цикл while
- read
), адаптация проста.
Например:
banana() {
printf 'banana to stdout %d\n' {1..10}
echo >&2 'banana to stderr'
return 42
}
. <({ berr=$({ mapfile -t bout < <(banana); } 2>&1; declare -p bout >&2); declare -p berr; } 2>&1)
и с кодом возврата:
. <({ berr=$({ mapfile -t bout< <(banana; bret=$?; declare -p bret >&3); } 3>&2 2>&1; declare -p bout >&2); declare -p berr; } 2>&1)
Ответ 3
Вы можете сделать:
OUT=$(myscript.sh 2> errFile)
ERR=$(<errFile)
Теперь $OUT
будет иметь стандартный вывод ваших script и $ERR
ошибок с вашим script.
Ответ 4
Простой, но не элегантный способ: перенаправить stderr во временный файл и затем прочитать его:
TMP=$(mktemp)
var=$(myscript.sh 2> "$TMP")
err=$(cat "$TMP")
rm "$TMP"
Ответ 5
Пока я не нашел способ захвата stderr и stdout для разделения переменных в bash, я отправляю обе те же переменные с...
result=$( { grep "JUNK" ./junk.txt; } 2>&1 )
... затем я проверяю статус выхода "$?" и действую соответствующим образом на данные в $result.
Ответ 6
# NAME
# capture - capture the stdout and stderr output of a command
# SYNOPSIS
# capture <result> <error> <command>
# DESCRIPTION
# This shell function captures the stdout and stderr output of <command> in
# the shell variables <result> and <error>.
# ARGUMENTS
# <result> - the name of the shell variable to capture stdout
# <error> - the name of the shell variable to capture stderr
# <command> - the command to execute
# ENVIRONMENT
# The following variables are mdified in the caller context:
# - <result>
# - <error>
# RESULT
# Retuns the exit code of <command>.
# SOURCE
capture ()
{
# Name of shell variable to capture the stdout of command.
result=$1
shift
# Name of shell variable to capture the stderr of command.
error=$1
shift
# Local AWK program to extract the error, the result, and the exit code
# parts of the captured output of command.
local evaloutput='
{
output [NR] = $0
}
END \
{
firstresultline = NR - output [NR - 1] - 1
if (Var == "error") \
{
for (i = 1; i < firstresultline; ++ i)
{
printf ("%s\n", output [i])
}
}
else if (Var == "result") \
{
for (i = firstresultline; i < NR - 1; ++ i)
{
printf ("%s\n", output [i])
}
}
else \
{
printf ("%d", output [NR])
}
}'
# Capture the stderr and stdout output of command, as well as its exit code.
local output="$(
{
local stdout
stdout="$($*)"
local exitcode=$?
printf "\n%s\n%d\n%d\n" \
"$stdout" "$(echo "$stdout" | wc -l)" "$exitcode"
} 2>&1)"
# extract the stderr, the stdout, and the exit code parts of the captured
# output of command.
printf -v $error "%s" \
"$(echo "$output" | gawk -v Var="error" "$evaloutput")"
printf -v $result "%s" \
"$(echo "$output" | gawk -v Var="result" "$evaloutput")"
return $(echo "$output" | gawk "$evaloutput")
}