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?

136
demandé sur l0b0 2009-06-07 20:38:11

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.)

68
répondu Jonathan Leffler 2012-05-31 01:46:15

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
60
répondu Dennis Williamson 2009-06-08 08:11:34

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)
48
répondu Chas. Owens 2009-06-07 16:51:05

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 ...
6
répondu tripleee 2018-02-11 18:18:34
# 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}
5
répondu human9 2013-11-28 15:52:43

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.

3
répondu tfga 2012-10-11 09:14:53

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.

2
répondu 2009-06-08 00:36:07
$ b=$( ( a=$( (echo stdout;echo stderr >&2) ) ) 2>&1 )
$ echo "a=>$a b=>$b"
a=>stdout b=>stderr
1
répondu Mario Wolff 2014-04-30 10:48:33

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.

1
répondu palmbardier 2015-08-19 14:34:29

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.

1
répondu SensorSmith 2018-05-22 20:36:39

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.

0
répondu Barton Chittenden 2012-05-31 05:25:03

en zsh:

{ . ./useless.sh > /dev/tty } 2>&1 | read ERROR
$ echo $ERROR
( your message )
0
répondu Ray Andrews 2015-11-14 17:04:04

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.

0
répondu Tom Hale 2018-10-02 09:52:47

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:

-1
répondu Alberto Salvia Novella 2017-12-29 06:10:35