web-dev-qa-db-fra.com

Capturez à la fois stdout et stderr dans Bash

Je connais cette syntaxe

var=`myscript.sh`

ou

var=$(myscript.sh)

Capture le résultat (stdout) de myscript.sh en var. Je pourrais rediriger stderr vers stdout si je voulais capturer les deux. Comment enregistrer chacun d'eux dans des variables distinctes?

Mon cas d'utilisation est ici si le code retour est différent de zéro, je veux faire écho stderr et supprimer sinon. Il peut y avoir d'autres façons de le faire, mais cette approche semble fonctionner, si c'est réellement possible.

37
djechlin

Il n'y a aucun moyen de capturer les deux sans fichier temporaire.

Vous pouvez capturer stderr en variable et passer stdout à l'écran utilisateur (exemple 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, c'est capturer stdout dans une variable et stderr dans une autre, en utilisant uniquement des redirections FD. Vous devez utiliser un fichier temporaire (ou un canal nommé) pour y parvenir.

28
zb'

Il y a une façon vraiment laide de capturer stderr et stdout dans deux variables distinctes sans fichiers temporaires (si vous aimez la plomberie), en utilisant substitution de processus , source et declare de manière appropriée. J'appellerai 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 une sortie standard de banana dans la variable bout et une erreur standard de banana dans la variable berr. Voici la magie qui y parviendra (Bash≥4 uniquement):

. <({ 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 profond:

bout=$(banana)

Ceci est juste la manière standard d'assigner à bout la sortie standard de banana, l'erreur standard étant affichée sur votre terminal.

Ensuite:

{ bout=$(banana); } 2>&1

assignera toujours à bout la sortie standard de banana, mais le stderr de banana est affiché sur le terminal via stdout (grâce à la redirection 2>&1.

Ensuite:

{ 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 intégré: ce sera bientôt réutilisé.

Ensuite:

berr=$({ bout=$(banana); } 2>&1; declare -p bout >&2); declare -p berr

affectera à berr le stderr de banana et affichera le contenu de berr avec declare.

À ce stade, vous aurez sur votre écran de terminal:

declare -- bout="banana to stdout"
declare -- berr="banana to stderr"

avec la ligne

declare -- bout="banana to stdout"

étant 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é via stdout.

Enfin, nous utilisons une substitution de processus pour source le contenu de ces lignes.


Vous avez également mentionné le code retour de la commande. Remplacez banana par:

banana() {
    echo "banana to stdout"
    echo >&2 "banana to stderr"
    return 42
}

Nous aurons également le code retour de banana dans la variable bret comme ceci:

. <({ berr=$({ bout=$(banana); bret=$?; } 2>&1; declare -p bout bret >&2); declare -p berr; } 2>&1)

Vous pouvez vous passer de sourcing et d'une substitution de processus en utilisant également eval (et cela fonctionne aussi avec Bash <4):

eval "$({ berr=$({ bout=$(banana); bret=$?; } 2>&1; declare -p bout bret >&2); declare -p berr; } 2>&1)"

Et tout cela est sûr, car les seules choses que nous sommes sourceing ou evaling sont obtenues à partir de declare -p et sera toujours correctement échappé.


Bien sûr, si vous voulez que la sortie soit dans un tableau (par exemple, avec mapfile, si vous utilisez Bash≥4 - sinon remplacez mapfile par un whileread loop), 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 code retour:

. <({ berr=$({ mapfile -t bout< <(banana; bret=$?; declare -p bret >&3); } 3>&2 2>&1; declare -p bout >&2); declare -p berr; } 2>&1)
41
gniourf_gniourf

Tu peux faire:

OUT=$(myscript.sh 2> errFile)
ERR=$(<errFile)

À présent $OUT aura une sortie standard de votre script et $ERR a une sortie d'erreur de votre script.

15
anubhava

Une manière simple mais pas élégante: redirigez stderr vers un fichier temporaire puis relisez-le:

TMP=$(mktemp)
var=$(myscript.sh 2> "$TMP")
err=$(cat "$TMP")
rm "$TMP"
7
jofel

Bien que je n'aie pas trouvé de 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 l'état de sortie "$?", Et j'agis de manière appropriée sur les données dans $ result.

3
jack