Disons que j'ai un script comme celui-ci:
inutile.sh
echo "This Is Error" 1>&2
echo "This Is Output"
Et j'ai un autre script Shell:
aussiUseless.sh
./useless.sh | sed 's/Output/Useless/'
Je veux capturer "This Is Error", ou tout autre stderr de useless.sh, dans une variable. Appelons cela ERREUR.
Notez que j'utilise stdout pour quelque chose. Je souhaite continuer à utiliser stdout, il est donc inutile de rediriger stderr vers stdout.
Donc, fondamentalement, je veux faire
./useless.sh 2> $ERROR | ...
mais cela ne fonctionne évidemment pas.
Je sais aussi que je pourrais faire
./useless.sh 2> /tmp/Error
ERROR=`cat /tmp/Error`
mais c'est moche et inutile.
Malheureusement, s'il n'y a pas de réponses, c'est ce que je vais devoir faire.
J'espère qu'il y a un autre moyen.
Quelqu'un a-t-il de meilleures idées?
Il serait plus judicieux de capturer le fichier d'erreur ainsi:
ERROR=$(</tmp/Error)
Le shell le reconnaît et il n’est pas nécessaire d’exécuter «cat
» pour obtenir les données.
La plus grande question est difficile. Je ne pense pas qu'il y ait un moyen facile de le faire. Vous devez créer l'intégralité du pipeline dans le sous-shell, en envoyant éventuellement sa sortie standard finale dans un fichier, afin de pouvoir rediriger les erreurs vers la sortie standard.
ERROR=$( { ./useless.sh | sed s/Output/Useless/ > outfile; } 2>&1 )
Notez que le point-virgule est nécessaire (dans les coquilles classiques - Bourne, Korn - bien sûr; probablement aussi dans Bash). Le '{}
' effectue la redirection E/S sur les commandes incluses. Tel qu'écrit, il capturerait aussi les erreurs de sed
.
AVERTISSEMENT: Code non encore testé - utilisation à vos risques et périls.
Cela vous permettra de canaliser la sortie de votre script useless.sh
par le biais d'une commande telle que sed
et d'enregistrer stderr
dans une variable nommée error
. Le résultat du canal est envoyé à stdout
pour être affiché ou pour être dirigé vers une autre commande.
Il configure quelques descripteurs de fichier 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
Stderr redirigé vers stdout, stdout vers/dev/null, puis utilisez les backticks ou $()
pour capturer le stderr redirigé:
ERROR=$(./useless.sh 2>&1 >/dev/null)
Il y a beaucoup de doublons pour cette question, dont beaucoup 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 en même temps.
if result=$(useless.sh 2>&1); then
stdout=$result
else
rc=$?
stderr=$result
fi
fonctionne pour le scénario courant dans lequel vous attendez une sortie appropriée en cas de succès ou un message de diagnostic sur stderr en cas d'échec.
Notez que les instructions de contrôle du shell examinent déjà $?
sous le capot; donc tout ce qui ressemble
cmd
if [ $? -eq 0 ], then ...
est juste une façon maladroite et unidiomatic 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:
#
# $1 - name of the (global) variable where the contents of stderr will be stored
# $2 - command to be executed
#
captureStderr()
{
local tmpFile=$(mktemp)
$2 2> $tmpFile
eval "$1=$(< $tmpFile)"
rm $tmpFile
}
Exemple d'utilisation:
captureStderr err "./useless.sh"
echo -$err-
Il utilise utilise un fichier temporaire. Mais au moins le truc laid est enveloppé dans une fonction.
Pour le bénéfice du lecteur, cette recette ici
Si vous voulez attraper stderr
d'une certaine command
dans var
, vous pouvez le faire
{ var="$( { command; } 2>&1 1>&3 3>&- )"; } 3>&1;
Ensuite, vous avez tout:
echo "command gives $? and stderr '$var'";
Si command
est simple (pas quelque chose comme a | b
), vous pouvez laisser le {}
intérieur absent:
{ var="$(command 2>&1 1>&3 3>&-)"; } 3>&1;
Enveloppé dans une fonction bash
- facilement réutilisable (nécessite probablement la version 3 et les versions ultérieures pour local -n
):
: catch-stderr var cmd [args..]
catch-stderr() { local -n v="$1"; shift && { v="$("$@" 2>&1 1>&3 3>&-)"; } 3>&1; }
A expliqué:
local -n
alias "$ 1" (qui est la variable pour catch-stderr
)3>&1
utilise le descripteur de fichier 3 pour enregistrer ses points stdout{ command; }
(ou "$ @") exécute ensuite la commande dans la capture de sortie $(..)
2>&1
redirige stderr
vers la capture de sortie $(..)
1>&3
redirige stdout
loin de la capture de sortie $(..)
vers le "extérieur" stdout
enregistré dans le descripteur de fichier 3. Notez que stderr
fait toujours référence à où FD 1 pointait avant: vers la sortie capturée $(..)
3>&-
ferme ensuite le descripteur de fichier 3 car il n'est plus nécessaire, de sorte que command
ne présente pas soudainement un descripteur de fichier ouvert inconnu. Notez que le shell externe a toujours FD 3 ouvert, mais command
ne le verra pas.lvm
se plaignent de descripteurs de fichier inattendus. Et lvm
se plaint de stderr
- exactement ce que nous allons capturer!Vous pouvez attraper tout autre descripteur de fichier avec cette recette, si vous vous adaptez en conséquence. Excepté le descripteur de fichier 1 bien sûr (ici la logique de redirection serait fausse, mais pour le descripteur de fichier 1, vous pouvez simplement utiliser var=$(command)
comme d'habitude).
Notez que cela sacrifie le descripteur de fichier 3. Si vous avez besoin de ce descripteur de fichier, n'hésitez pas à changer le numéro. Mais sachez que certains shells (des années 1980) pourraient comprendre 99>&1
comme argument 9
suivi de 9>&1
(ce n'est pas un problème pour bash
).
Notez également qu’il n’est pas particulièrement facile de rendre ce FD 3 configurable au moyen d’une variable. Cela rend les choses très illisibles:
: catch-var-from-fd-by-fd variable fd-to-catch fd-to-sacrifice command [args..]
catch-var-from-fd-by-fd()
{
local -n v="$1";
local fd1="$2" fd2="$3";
shift 3 || return;
eval exec "$fd2>&1";
v="$(eval '"$@"' "$fd1>&1" "1>&$fd2" "$fd2>&-")";
eval exec "$fd2>&-";
}
Note de sécurité: Les 3 premiers arguments de
catch-var-from-fd-by-fd
ne doivent pas provenir d'une 3ème partie. Toujours leur donner explicitement de manière "statique".Donc non-non-non
catch-var-from-fd-by-fd $var $fda $fdb $command
, ne faites jamais ça!Si vous passez un nom de variable, procédez au moins comme suit:
local -n var="$var"; catch-var-from-fd-by-fd var 3 5 $command
Cela ne vous protégera toujours pas contre tous les exploits, mais vous aidera au moins à détecter et à éviter les erreurs de script courantes.
Remarques:
catch-var-from-fd-by-fd var 2 3 cmd..
est identique à catch-stderr var cmd..
shift || return
n'est qu'un moyen d'éviter les erreurs laides si vous oubliez de donner le nombre correct d'arguments. Terminer le Shell serait peut-être un autre moyen (mais cela rend difficile le test depuis la ligne de commande).exec
, mais elle devient vraiment laide.bash
, de sorte que local -n
n'est pas nécessaire. Cependant, vous ne pouvez pas utiliser de variables locales et cela devient extrêmement laid!eval
s sont utilisés de manière sécurisée. Habituellement, eval
est considéré comme dangereux. Cependant, dans ce cas, il n’est pas plus diabolique que d’utiliser "$@"
(pour exécuter des commandes arbitraires). Cependant, veillez à utiliser les citations exactes et correctes comme indiqué ici (sinon, il devient très très dangereux).C’est un problème intéressant auquel j’espérais une solution élégante. Malheureusement, je me retrouve avec une solution similaire à celle de M. Leffler, mais j'ajouterai que vous pouvez appeler inutilisable depuis l'intérieur d'une fonction Bash pour améliorer la lisibilité:
#!/bin/bash fonction inutile { /tmp/useless.sh | sed 's/Output/Inutile /' } ERROR = $ (inutilisable) echo $ ERROR
Tous les autres types de redirection de sortie doivent être sauvegardés par un fichier temporaire.
Capture AND Print stderr
ERROR=$( ./useless.sh 3>&1 1>&2 2>&3 | tee /dev/fd/2 )
Panne
Vous pouvez utiliser $()
pour capturer stdout, mais vous souhaitez capturer stderr à la place. Donc, vous échangez stdout et stderr. Utilisation de fd 3 comme stockage temporaire dans l’algorithme de permutation standard.
Si vous souhaitez capturer ET imprimer, utilisez tee
pour créer une copie. Dans ce cas, la sortie de tee
sera capturée par $()
plutôt que d'aller à la console, mais stderr (de tee
) ira toujours vers la console, nous l'utilisons donc comme deuxième sortie de tee
via le fichier spécial /dev/fd/2
puisque tee
attend un chemin du fichier plutôt qu'un nombre fd.
NOTE: Il y a énormément de redirections sur une seule ligne et l'ordre compte. $()
saisit le stdout de tee
à la fin du pipeline et le pipeline lui-même achemine la stdout de ./useless.sh
vers le stdin de tee
APRÈS que nous ayons échangé stdin et stdout pour ./useless.sh
.
Utilisation de la sortie standard de ./useless.sh
L'OP a déclaré qu'il souhaitait toujours utiliser (pas seulement imprimer) la sortie standard, telle que ./useless.sh | sed 's/Output/Useless/'
.
Pas de problème, faites-le AVANT de permuter stdout et stderr. Je recommande de le déplacer dans une fonction ou un fichier (also-useless.sh) et de l'appeler à la place 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 car $()
n'en fera qu'un à la fois et crée un sous-shell à partir duquel vous ne pouvez pas retourner de variables.
$ b=$( ( a=$( (echo stdout;echo stderr >&2) ) ) 2>&1 )
$ echo "a=>$a b=>$b"
a=>stdout b=>stderr
Cet article m'a aidé à trouver une solution similaire pour mes propres besoins:
MESSAGE=`{ echo $ERROR_MESSAGE | format_logs.py --level=ERROR; } 2>&1`
Ensuite, tant que notre message n'est pas une chaîne vide, nous le transmettons à d'autres éléments. Cela nous permettra de savoir si notre format_logs.py a échoué avec une sorte d’exception python.
Si vous souhaitez ignorer l'utilisation d'un fichier temporaire, vous pouvez éventuellement utiliser la substitution de processus. Je n'ai pas encore réussi à le faire fonctionner. 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
Toutefois
$ ./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'emballe STDIN dans >( )
avec quelque chose dans $()
dans le but de capturer cela dans 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 de/dev/fd qui appartient au processus parent.
La substitution de processus m'a permis de travailler avec un flux de données qui n'est plus dans STDERR. Malheureusement, il semble que je ne sois pas capable de le manipuler comme je le souhaite.
En zsh:
{ . ./useless.sh > /dev/tty } 2>&1 | read ERROR
$ echo $ERROR
( your message )
STDERR peut être capturé avec une magie de redirection:
$ { 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
) est réalisée à l'intérieur du {
}
le plus interne. Si vous exécutez une commande simple (par exemple, pas un tuyau), vous pouvez supprimer ces accolades internes.
Vous ne pouvez pas diriger en dehors de la commande, car la tuyauterie crée un sous-shell dans bash
et zsh
, et l'affectation à la variable du sous-shell ne serait pas disponible pour le shell actuel.
Dans bash
, il serait préférable de ne pas supposer que le descripteur de fichier 3 est inutilisé:
{ 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 error proofing vos commandes:
execute [INVOKING-FUNCTION] [COMMAND]
execute () {
function="${1}"
command="${2}"
error=$(eval "${command}" 2>&1 >"/dev/null")
if [ ${?} -ne 0 ]; then
echo "${function}: ${error}"
exit 1
fi
}
Inspired dans la fabrication sans gaspillage:
{ ERROR=$(./useless.sh 2>&1 1>&$out); } {out}>&1
echo "-"
echo $ERROR
Produira:
This Is Output
-
This Is Error