Comment stocker l'erreur standard dans une variable dans un script Bash
disons que j'ai un script comme le suivant:
useless.sh
echo "This Is Error" 1>&2
echo "This Is Output"
et j'ai un autre script shell:
alsoUseless.sh
./useless.sh | sed 's/Output/Useless/'
je veux capturer "Ceci est une erreur", ou tout autre stderr de useless.sh, dans une variable. Appelons ça une erreur.
notez que j'utilise stdout pour quelque chose. Je veux continuer à utiliser stdout, donc rediriger stderr dans stdout n'est pas utile dans ce cas.
Donc, en gros, je veux faire
./useless.sh 2> $ERROR | ...
mais ça ne marche pas.
je sais aussi que je pouvais le faire
./useless.sh 2> /tmp/Error
ERROR=`cat /tmp/Error`
mais c'est laid et inutile.
malheureusement, s'il n'y a pas de réponse ici, c'est ce que je vais devoir faire.
j'espère qu'il y a un autre moyen.
N'importe qui ont des idées?
14 réponses
il serait plus approprié de capturer le fichier d'erreur ainsi:
ERROR=$(</tmp/Error)
le shell le reconnaît et n'a pas besoin d'exécuter ' cat
" pour obtenir les données.
la grande question est difficile. Je ne pense pas qu'il ya un moyen facile de le faire. Vous devez construire le pipeline entier dans le sous-shell, éventuellement en envoyant sa sortie standard finale à un fichier, afin que vous puissiez rediriger les erreurs vers la sortie standard.
ERROR=$( { ./useless.sh | sed s/Output/Useless/ > outfile; } 2>&1 )
notez que le semi-côlon est nécessaire (en coquilles classiques - Bourne, Korn - pour sûr; probablement Bash aussi). Le ' {}
' redirige les e/s sur les commandes incluses. Comme écrit, il capturerait des erreurs de sed
aussi.
(Officiellement du code non testé - utilisez à votre propre risque.)
alsoUseless.sh
cela vous permettra de pipe la sortie de votre script useless.sh
par une commande telle que sed
et de sauvegarder le stderr
dans une variable nommée error
. Le résultat de la pipe est envoyé à stdout
pour affichage ou pour être pipé dans une autre commande.
il met en place quelques descripteurs de fichiers supplémentaires pour gérer les redirections nécessaires à cette fin.
#!/bin/bash
exec 3>&1 4>&2 #set up extra file descriptors
error=$( { ./useless.sh | sed 's/Output/Useless/' 2>&4 1>&3; } 2>&1 )
echo "The message is \"${error}.\""
exec 3>&- 4>&- # release the extra file descriptors
redirigé stderr vers stdout, stdout vers / dev / null, puis utiliser les backticks ou $()
pour capturer le stderr redirigé:
ERROR=$(./useless.sh 2>&1 >/dev/null)
il y a beaucoup de duplicata pour cette question, beaucoup de qui ont un scénario d'utilisation légèrement plus simple où vous ne voulez pas capturer stderr et stdout et le code de sortie tout en même temps.
if result=$(useless.sh 2>&1); then
stdout=$result
else
rc=$?
stderr=$result
fi
fonctionne pour le scénario commun où vous attendez soit une sortie correcte en cas de succès, ou un message de diagnostic sur stderr en cas d'échec.
noter que le shell déclarations de contrôle déjà examiner $?
sous la hotte; donc tout ce qui ressemble à
cmd
if [ $? -eq 0 ], then ...
est juste une façon maladroite et unidiomatique de dire
if cmd; then ...
# command receives its input from stdin.
# command sends its output to stdout.
exec 3>&1
stderr="$(command </dev/stdin 2>&1 1>&3)"
exitcode="${?}"
echo "STDERR: $stderr"
exit ${exitcode}
Voici comment je l'ai fait:
#
# - name of the (global) variable where the contents of stderr will be stored
# - command to be executed
#
captureStderr()
{
local tmpFile=$(mktemp)
2> $tmpFile
eval "=$(< $tmpFile)"
rm $tmpFile
}
exemple d'utilisation:
captureStderr err "./useless.sh"
echo -$err-
Il ne utiliser un fichier temporaire. Mais au moins le vilain truc est enveloppé dans une fonction.
c'est un problème intéressant auquel j'espérais trouver une solution élégante. Malheureusement, je me retrouve avec une solution similaire à M. Leffler, mais je vais ajouter que vous pouvez appeler inutile de l'intérieur d'une fonction Bash pour une meilleure lisibilité:
#!/bin/bash function useless { /tmp/useless.sh | sed 's/Output/Useless/' } ERROR=$(useless) echo $ERROR
toute autre redirection de sortie doit être soutenue par un fichier temporaire.
$ b=$( ( a=$( (echo stdout;echo stderr >&2) ) ) 2>&1 )
$ echo "a=>$a b=>$b"
a=>stdout b=>stderr
ce billet m'a aidé à trouver une solution similaire à mes propres fins:
MESSAGE=`{ echo $ERROR_MESSAGE | format_logs.py --level=ERROR; } 2>&1`
aussi longtemps que notre MESSAGE n'est pas une chaîne vide, nous le transmettons à d'autres choses. Cela nous permettra de savoir si notre format_logs.py échoué avec une sorte d'exception python.
Capturer ET Print stderr
ERROR=$( ./useless.sh 3>&1 1>&2 2>&3 | tee /dev/fd/2 )
ventilation
vous pouvez utiliser $()
pour capturer stdout, mais vous voulez capturer stderr à la place. Donc vous échangez stdout et stderr. Utilisant fd 3 comme stockage temporaire dans l'algorithme standard de swap.
si vous voulez capturer et imprimer, utilisez tee
pour faire un duplicata. Dans ce cas, la sortie de tee
sera capturé par $()
plutôt que d'aller à la console, mais stderr(de tee
) ira toujours à la console ainsi nous utilisons que comme deuxième sortie pour tee
via le fichier spécial /dev/fd/2
depuis tee
attend un chemin de fichier plutôt qu'un nombre fd.
NOTE: C'est beaucoup de redirections dans une seule ligne et l'ordre importe. $()
s'empare du stdout de tee
à l'extrémité du pipeline et le le pipeline lui-même achemine stdout de ./useless.sh
au stdin de tee
après que nous ayons échangé stdin et stdout pour ./useless.sh
.
utilisant stdout of. /useless.sh
l'OP a dit qu'il voulait toujours utiliser (pas seulement imprimer) stdout, comme ./useless.sh | sed 's/Output/Useless/'
.
Pas de problème, juste le faire AVANT l'échange de stdout et stderr. Je vous recommande de le déplacer dans une fonction ou d'un fichier (also-useless.sh) et l'appel que dans lieu de ./useless.sh dans la ligne ci-dessus.
cependant, si vous voulez capturer stdout et stderr, alors je pense que vous devez vous rabattre sur les fichiers temporaires parce que $()
ne fera qu'un à la fois et il fait un sous-puits à partir duquel vous ne pouvez pas retourner les variables.
Si vous voulez contourner l'utilisation d'un fichier temporaire, vous pouvez être en mesure d'utiliser le processus de substitution. Je n'ai pas encore eu à le faire fonctionner encore. C'était ma première tentative:
$ .useless.sh 2> >( ERROR=$(<) )
-bash: command substitution: line 42: syntax error near unexpected token `)'
-bash: command substitution: line 42: `<)'
puis j'ai essayé
$ ./useless.sh 2> >( ERROR=$( cat <() ) )
This Is Output
$ echo $ERROR # $ERROR is empty
cependant
$ ./useless.sh 2> >( cat <() > asdf.txt )
This Is Output
$ cat asdf.txt
This Is Error
donc la substitution de processus fait généralement la bonne chose... malheureusement, chaque fois que J'enroule STDIN à l'intérieur de >( )
avec quelque chose $()
dans une tentative de capturer cela à une variable, je perds le contenu de $()
. Je pense que c'est parce que $()
lance un sous-processus qui n'a plus accès au descripteur de fichier dans /dev/FD qui appartient au processus parent.
la substitution de processus m'a acheté la capacité de travailler avec un flux de données qui n'est plus dans STDERR, malheureusement je ne semble pas être en mesure de le manipuler de la façon que je veux.
en zsh:
{ . ./useless.sh > /dev/tty } 2>&1 | read ERROR
$ echo $ERROR
( your message )
POSIX
STDERR peut être capturé avec certains de redirection de la magie:
$ { error=$( { { ls -ld /XXXX /bin | tr o Z ; } 1>&3 ; } 2>&1); } 3>&1
lrwxrwxrwx 1 rZZt rZZt 7 Aug 22 15:44 /bin -> usr/bin/
$ echo $error
ls: cannot access '/XXXX': No such file or directory
notez que la tuyauterie de STDOUT de la commande (ici ls
) se fait à l'intérieur du plus proche {
}
. Si vous exécutez une commande simple (par exemple, pas un tuyau), vous pouvez enlever ces attaches intérieures.
vous ne pouvez pas passer à l'extérieur de la commande comme la tuyauterie fait un subshell dans bash
et zsh
, et le l'affectation à la variable dans le sous-puits ne serait pas disponible pour l'interpréteur de commandes actuel.
bash
dans bash
, il serait préférable de ne pas supposer que le descripteur de fichier 3 n'est pas utilisé:
{ error=$( { { ls -ld /XXXX /bin | tr o Z ; } 1>&$tmp ; } 2>&1); } {tmp}>&1;
exec {tmp}>&- # With this syntax the FD stays open
notez que cela ne fonctionne pas dans zsh
.
merci à cette réponse pour l'idée générale.
Pour erreur de vérification linguistique vos commandes:
execute [INVOKING-FUNCTION] [COMMAND]
execute () {
error=$( 2>&1 >/dev/null)
if [ $? -ne 0 ]; then
echo ": $error"
exit 1
fi
}
inspiré dans la fabrication sans gaspillage: