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.
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.
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 source
ing ou eval
ing 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 while
– read
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)
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.
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"
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.