Capture de stdout et de stderr dans Bash [dupliquer]
cette question a déjà une réponse ici:
je connais cette syntaxe
var=`myscript.sh`
ou
var=$(myscript.sh)
capturera le résultat ( stdout
) de myscript.sh
en var
. Je pourrais rediriger stderr
dans stdout
si je voulais capturer les deux. Comment sauvegarder chacun d'eux pour séparer les variables?
mon cas d'utilisation ici est si le code de retour est nonzero je veux faire écho stderr
et supprimer autrement. Il peut y avoir d'autres façons de le faire, mais cette approche semble fonctionner, si c'est réellement possible.
6 réponses
il n'y a aucun moyen de capturer les deux sans fichier temp.
vous pouvez capturer stderr à variable et passer stdout à l'écran de l'utilisateur (échantillon de ici ):
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
mais il n'y a aucun moyen de capturer à la fois stdout et stderr:
ce que vous ne pouvez pas faire est de capturer stdout dans une variable, et stderr dans une autre, en utilisant seulement des redirections FD. Vous doit utilisation d'un fichier temporaire (ou un tube nommé) à atteindre.
il y a une façon vraiment laide de capturer stderr
et stdout
dans deux variables séparées sans fichiers temporaires (si vous aimez la plomberie), en utilisant substitution de processus , source
, et declare
de manière appropriée. Je vais appeler votre commande banana
. Vous pouvez imiter une telle commande avec une fonction:
banana() {
echo "banana to stdout"
echo >&2 "banana to stderr"
}
je suppose que vous voulez la sortie standard de banana
dans la variable bout
et l'erreur standard de banana
dans la variable berr
. Voici la magie qui permettra cela (Bash≥4 seulement):
. <({ berr=$({ bout=$(banana); } 2>&1; declare -p bout >&2); declare -p berr; } 2>&1)
alors, que se passe-t-il ici?
commençons par le terme le plus proche:
bout=$(banana)
c'est juste la façon standard d'attribuer à bout
la sortie standard de banana
, l'erreur standard étant affichée sur votre terminal.
alors:
{ bout=$(banana); } 2>&1
assignera toujours à bout
le stdout de banana
, mais le stderr de banana
est affiché sur le terminal via stdout (merci à la redirection 2>&1
.
puis:
{ bout=$(banana); } 2>&1; declare -p bout >&2
fera comme ci-dessus, mais affichera également sur le terminal (via stderr) le contenu de bout
avec le declare
builtin: celui-ci sera réutilisé prochainement.
alors:
berr=$({ bout=$(banana); } 2>&1; declare -p bout >&2); declare -p berr
assignera à berr
le stderr de banana
et affichera le contenu de berr
avec declare
.
à ce point, vous aurez sur votre écran terminal:
declare -- bout="banana to stdout"
declare -- berr="banana to stderr"
avec la ligne
declare -- bout="banana to stdout"
affiché via stderr.
une redirection finale:
{ berr=$({ bout=$(banana); } 2>&1; declare -p bout >&2); declare -p berr; } 2>&1
aura le précédent affiché sur la sortie standard stdout.
enfin, nous utilisons un substitution de procédé à source le contenu de ces lignes.
vous avez aussi mentionné le code de retour de la commande. Remplacer banana
par:
banana() {
echo "banana to stdout"
echo >&2 "banana to stderr"
return 42
}
nous aurons aussi le code de retour de banana
dans la variable bret
comme ainsi:
. <({ berr=$({ bout=$(banana); bret=$?; } 2>&1; declare -p bout bret >&2); declare -p berr; } 2>&1)
vous pouvez faire sans sourcing et une substitution de processus en utilisant eval
aussi (et il fonctionne avec Bash<4 aussi):
eval "$({ berr=$({ bout=$(banana); bret=$?; } 2>&1; declare -p bout bret >&2); declare -p berr; } 2>&1)"
et tout cela est sûr, parce que la seule chose que nous sommes source
ing ou eval
ing sont obtenus à partir de declare -p
et sera toujours correctement échappé.
bien sûr, si vous voulez la sortie dans un tableau (par exemple, avec mapfile
, si vous utilisez Bash≥4-sinon remplacer mapfile
par while
- read
boucle), l'adaptation est simple.
par exemple:
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)
et avec le code de retour:
. <({ berr=$({ mapfile -t bout< <(banana; bret=$?; declare -p bret >&3); } 3>&2 2>&1; declare -p bout >&2); declare -p berr; } 2>&1)
Vous pouvez le faire:
OUT=$(myscript.sh 2> errFile)
ERR=$(<errFile)
maintenant $OUT
aura la sortie standard de votre script et $ERR
aura la sortie d'erreur de votre script.
un moyen facile, mais pas élégant: rediriger stderr vers un fichier temporaire et puis le relire:
TMP=$(mktemp)
var=$(myscript.sh 2> "$TMP")
err=$(cat "$TMP")
rm "$TMP"
bien que je n'ai pas trouvé un moyen de capturer stderr et stdout pour séparer les variables dans bash, j'envoie les deux à la même variable avec...
result=$( { grep "JUNK" ./junk.txt; } 2>&1 )
... puis je vérifie le statut de sortie"$?"et agir de manière appropriée sur les données dans $result.
# 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=
shift
# Name of shell variable to capture the stderr of command.
error=
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] = "151900920"
}
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")
}