Pour le bénéfice du lecteur, cette recette ici
- peut être réutilisé comme oneliner pour capturer stderr dans une variable
- donne toujours accès au code retour de la commande
- Sacrifie un descripteur de fichier temporaire 3 (qui peut être modifié par vous bien sûr)
- Et n'expose pas ces descripteurs de fichiers temporaires à la commande interne
Si vous voulez attraper stderrde certains commanden varvous pouvez faire
{ var="$( { command; } 2>&1 1>&3 3>&- )"; } 3>&1;
Ensuite, vous avez tout:
echo "command gives $? and stderr '$var'";
Si commandest simple (pas quelque chose comme a | b) vous pouvez laisser l'intérieur {}loin:
{ var="$(command 2>&1 1>&3 3>&-)"; } 3>&1;
Enveloppé dans une fonction réutilisable facile bash(nécessite probablement la version 3 et supérieure pour local -n):
: catch-stderr var cmd [args..]
catch-stderr() { local -n v="$1"; shift && { v="$("$@" 2>&1 1>&3 3>&-)"; } 3>&1; }
Expliqué:
local -nalias "$ 1" (qui est la variable pour catch-stderr)
3>&1 utilise le descripteur de fichier 3 pour y enregistrer les points stdout
{ command; } (ou "$ @") exécute alors la commande dans la capture de sortie $(..)
- Veuillez noter que l'ordre exact est important ici (le faire dans le mauvais sens mélange mal les descripteurs de fichiers):
2>&1redirige stderrvers la capture de sortie$(..)
1>&3redirige stdoutloin de la capture de sortie $(..)vers le "externe" stdoutqui a été enregistré dans le descripteur de fichier 3. Notez que stderrfait toujours référence à l'endroit où FD 1 pointait auparavant: à la capture de sortie$(..)
3>&-puis ferme le descripteur de fichier 3 car il n'est plus nécessaire, de sorte que commandtout à coup un descripteur de fichier ouvert inconnu n'apparaisse pas. Notez que la coque externe a toujours FD 3 ouvert, mais commandne le verra pas.
- Ce dernier est important, car certains programmes comme se
lvmplaignent de descripteurs de fichiers inattendus. Et se lvmplaint de stderr- exactement ce que nous allons capturer!
Vous pouvez attraper n'importe quel autre descripteur de fichier avec cette recette, si vous vous adaptez en conséquence. Sauf 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 l'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 être interprétés 99>&1comme un argument 9suivi de 9>&1(ce n'est pas un problème pour bash).
Notez également qu'il n'est pas particulièrement simple de rendre ce FD 3 configurable via 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-fdne doivent pas provenir d'un tiers. Donnez-les toujours explicitement de façon «statique».
Alors non catch-var-from-fd-by-fd $var $fda $fdb $command, non , ne fais jamais ça!
Si vous passez un nom de variable, faites-le 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 aidera au moins à détecter et à éviter les erreurs de script courantes.
Remarques:
catch-var-from-fd-by-fd var 2 3 cmd.. est le même que catch-stderr var cmd..
shift || returnest juste un moyen d'éviter de vilaines erreurs au cas où vous oublieriez de donner le nombre correct d'arguments. Peut-être que terminer le shell serait une autre façon (mais cela rend le test difficile à partir de la ligne de commande).
- La routine a été écrite de telle sorte qu'elle est plus facile à comprendre. On peut réécrire la fonction de telle sorte qu'elle n'en ait pas besoin
exec, mais alors ça devient vraiment moche.
- Cette routine peut également être réécrite pour non-
bashainsi que ce n'est pas nécessaire local -n. Cependant, vous ne pouvez pas utiliser de variables locales et cela devient extrêmement moche!
- Notez également que les
evals sont utilisés de manière sûre. evalEst généralement considéré comme dangereux. Cependant, dans ce cas, ce n'est pas plus mal que d'utiliser "$@"(pour exécuter des commandes arbitraires). Cependant, assurez-vous d'utiliser la citation exacte et correcte comme indiqué ici (sinon cela devient très très dangereux ).
ERROR=$(./useless.sh | sed 's/Output/Useless/' 2>&1 1>/dev/ttyX)