bash - redirect stdout and stderr to null
Capture tanto stdout como stderr en Bash (6)
Esta pregunta ya tiene una respuesta aquí:
- Captura stdout y stderr en diferentes variables 13 respuestas
Conozco esta sintaxis
var=`myscript.sh`
o
var=$(myscript.sh)
Capturará el resultado ( stdout
) de myscript.sh
en var
. Podría redirigir stderr
a stdout
si quisiera capturar ambos. ¿Cómo guardar cada una de ellas en variables separadas?
Mi caso de uso aquí es si el código de retorno es distinto de cero. Quiero hacer eco de stderr
y suprimir lo contrario. Puede haber otras formas de hacer esto, pero este enfoque parece que funcionará, si es posible.
Hay una manera realmente fea de capturar stderr
y stdout
en dos variables separadas sin archivos temporales (si le gusta la plomería), usando la sustitución de procesos , la source
y la declare
apropiada. Llamaré a tu comando banana
. Puedes imitar este comando con una función:
banana() {
echo "banana to stdout"
echo >&2 "banana to stderr"
}
Asumiré que desea una salida estándar de banana
en el bout
variable y un error estándar de banana
en la variable berr
. Aquí está la magia que lo logrará (solo Bash≥4):
. <({ berr=$({ bout=$(banana); } 2>&1; declare -p bout >&2); declare -p berr; } 2>&1)
Entonces, ¿qué está pasando aquí?
Empecemos por el término más interno:
bout=$(banana)
Esta es solo la forma estándar de asignar a la salida estándar de banana
, ya que el error estándar se muestra en su terminal.
Entonces:
{ bout=$(banana); } 2>&1
aún se asignará a la versión estándar de banana
, pero la versión de banana
se muestra en la terminal a través de la salida estándar (gracias a la redirección 2>&1
.
Entonces:
{ bout=$(banana); } 2>&1; declare -p bout >&2
hará lo mismo que antes, pero también mostrará en el terminal (a través de stderr) el contenido de la bout
con la declare
incorporada: esto se reutilizará pronto.
Entonces:
berr=$({ bout=$(banana); } 2>&1; declare -p bout >&2); declare -p berr
asignará a berr
el stderr de banana
y mostrará el contenido de berr
con declare
.
En este punto, tendrás en la pantalla de tu terminal:
declare -- bout="banana to stdout"
declare -- berr="banana to stderr"
con la linea
declare -- bout="banana to stdout"
se muestra a través de stderr.
Una redirección final:
{ berr=$({ bout=$(banana); } 2>&1; declare -p bout >&2); declare -p berr; } 2>&1
tendrá el anterior mostrado a través de la salida estándar.
Finalmente, utilizamos una sustitución de procesos para obtener el contenido de estas líneas.
Usted mencionó el código de retorno del comando también. Cambiar el banana
a:
banana() {
echo "banana to stdout"
echo >&2 "banana to stderr"
return 42
}
También tendremos el código de retorno de banana
en la variable bret
así:
. <({ berr=$({ bout=$(banana); bret=$?; } 2>&1; declare -p bout bret >&2); declare -p berr; } 2>&1)
Puede prescindir de la obtención de recursos y una sustitución de procesos utilizando también eval
(y también funciona con Bash <4):
eval "$({ berr=$({ bout=$(banana); bret=$?; } 2>&1; declare -p bout bret >&2); declare -p berr; } 2>&1)"
Y todo esto es seguro, porque las únicas cosas que estamos generando o eval
se obtienen de declare -p
y siempre se escaparán correctamente.
Por supuesto, si desea la salida en una matriz (por ejemplo, con mapfile
, si está usando Bash≥4, de lo contrario reemplace mapfile
con un ciclo de read
), la adaptación es sencilla.
Por ejemplo:
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)
y con el código de retorno:
. <({ berr=$({ mapfile -t bout< <(banana; bret=$?; declare -p bret >&3); } 3>&2 2>&1; declare -p bout >&2); declare -p berr; } 2>&1)
No hay forma de capturar ambos sin el archivo temporal.
Puede capturar stderr a variable y pasar stdout a la pantalla de usuario (muestra de mywiki.wooledge.org/BashFAQ/002 ):
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
Pero no hay forma de capturar tanto stdout como stderr:
Lo que no puede hacer es capturar stdout en una variable, y stderr en otra, usando solo redirecciones FD. Debe utilizar un archivo temporal (o una canalización con nombre) para lograrlo.
Si bien no he encontrado una forma de capturar stderr y stdout para separar variables en bash, envío ambas a la misma variable con ...
result=$( { grep "JUNK" ./junk.txt; } 2>&1 )
... luego verifico el estado de salida "$?", Y actúo apropiadamente sobre los datos en $ resultado.
Tu puedes hacer:
OUT=$(myscript.sh 2> errFile)
ERR=$(<errFile)
Ahora $OUT
tendrá una salida estándar de su script y $ERR
tiene una salida de error de su script.
Una forma fácil, pero no elegante: redirigir stderr a un archivo temporal y luego leerlo de nuevo:
TMP=$(mktemp)
var=$(myscript.sh 2> "$TMP")
err=$(cat "$TMP")
rm "$TMP"
# 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''s 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")
}