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 veux continuer à utiliser stdout, donc la redirection de stderr vers stdout n'est pas utile, dans ce cas.
Donc, en gros, 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, si aucune réponse n'apparaît ici, 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?
ERROR=$(./useless.sh | sed 's/Output/Useless/' 2>&1 1>/dev/ttyX)
Réponses:
Il serait plus simple de capturer le fichier d'erreur ainsi:
Le shell reconnaît cela et n'a pas besoin 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 auriez à construire le pipeline entier dans le sous-shell, en envoyant finalement sa sortie standard finale à un fichier, afin que vous puissiez rediriger les erreurs vers la sortie standard.
Notez que le point-virgule est nécessaire (dans les shells classiques - Bourne, Korn - bien sûr; probablement dans Bash aussi). Le '
{}
' effectue la redirection d'E / S sur les commandes incluses. Tel qu'il est écrit, il capturerait également les erreurssed
.la source
/dev/null
au lieu deoutfile
(Si vous êtes comme moi, vous avez trouvé cette question via Google et n'avez pas les mêmes exigences que l'OP)stdout
et lesstderr
allers-retours. Mais attention , comme il est dit ici : en bash, il vaudrait mieux ne pas supposer que le descripteur de fichier 3 est inutilisé " .aussiUseless.sh
Cela vous permettra de diriger la sortie de votre
useless.sh
script via une commande telle quesed
et d'enregistrer lestderr
dans une variable nomméeerror
. Le résultat du tube est envoyé àstdout
pour affichage ou pour être redirigé vers une autre commande.Il met en place quelques descripteurs de fichiers supplémentaires pour gérer les redirections nécessaires pour ce faire.
#!/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
la source
stderr
etstdout
dans les variables?dry_run
fonction qui peut choisir de manière fiable entre faire écho à ses arguments et les exécuter, que la commande exécutée à sec soit dirigée vers un autre fichier.read
n'accepte pas l'entrée d'un tube. Vous pouvez utiliser d'autres techniques pour réaliser ce que vous essayez de démontrer.Redirigé stderr vers stdout, stdout vers / dev / null, puis utilisez les backticks ou
$()
pour capturer le stderr redirigé:la source
PY_VERSION="$(python --version 2>&1)"
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 où vous attendez soit une sortie correcte en cas de succès, soit 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 ressemblecmd if [ $? -eq 0 ], then ...
est juste une façon maladroite et unidiomatique de dire
if cmd; then ...
la source
# 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}
la source
command
est un mauvais choix ici, dans la mesure où il existe en fait un builtin portant ce nom. Pourrait le rendreyourCommand
ou tel, pour être plus explicite.Pour le bénéfice du lecteur, cette recette ici
Si vous voulez attraper
stderr
de certainscommand
envar
vous pouvez 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 commea | 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 pourlocal -n
):: catch-stderr var cmd [args..] catch-stderr() { local -n v="$1"; shift && { v="$("$@" 2>&1 1>&3 3>&-)"; } 3>&1; }
Expliqué:
local -n
alias "$ 1" (qui est la variable pourcatch-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$(..)
2>&1
redirigestderr
vers la capture de sortie$(..)
1>&3
redirigestdout
loin de la capture de sortie$(..)
vers le "externe"stdout
qui a été enregistré dans le descripteur de fichier 3. Notez questderr
fait 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 quecommand
tout à coup un descripteur de fichier ouvert inconnu n'apparaisse pas. Notez que la coque externe a toujours FD 3 ouvert, maiscommand
ne le verra pas.lvm
plaignent de descripteurs de fichiers inattendus. Et selvm
plaint destderr
- 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>&1
comme un argument9
suivi de9>&1
(ce n'est pas un problème pourbash
).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>&-"; }
Remarques:
catch-var-from-fd-by-fd var 2 3 cmd..
est le même quecatch-stderr var cmd..
shift || return
est 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).exec
, mais alors ça devient vraiment moche.bash
ainsi que ce n'est pas nécessairelocal -n
. Cependant, vous ne pouvez pas utiliser de variables locales et cela devient extrêmement moche!eval
s sont utilisés de manière sûre.eval
Est 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 ).la source
POSIX
STDERR peut être capturé avec une certaine 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 le piping de STDOUT de la commande (ici
ls
) se fait à l'intérieur du plus profond{
}
. Si vous exécutez une commande simple (par exemple, pas un tube), vous pouvez supprimer ces accolades internes.Vous ne pouvez pas canaliser en dehors de la commande car la canalisation crée un sous-shell dans
bash
etzsh
, et l'affectation à la variable dans le sous-shell ne serait pas disponible pour le shell actuel.frapper
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.
la source
tmp
que dans ce cas, il s'agit simplement d'une variable qui stocke un descripteur de fichier dont vous savez qu'il n'est pas utilisé. Par exemple, sitmp=3
then1>&$tmp
devenait1>&3
et que la commande serait la même que celle expliquée précédemment (elle stockeraitstdout
(1
) dans le descripteur de fichier3
, questderr
(2
) iraitstdout
et serait stocké dans laerror
variable, et enfin le contenu diffusé dans le descripteur de fichier3
revient au descripteur de fichier1
, c'est-à-direstdout
, à cause de{tmp}>&1
cela se transforme en3>&1
, si j'ai bien compris).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 n'utiliser un fichier temporaire. Mais au moins, le truc laid est enveloppé dans une fonction.
la source
eval
. Par exemple,printf -v "$1" '%s' "$(<tmpFile)"
ne risque pas d'exécuter du code arbitraire si votreTMPDIR
variable a été définie sur une valeur malveillante (ou si votre nom de variable de destination contient une telle valeur).rm -- "$tmpFile"
est plus robuste querm $tmpFile
.C'est un problème intéressant auquel j'espérais qu'il y avait une solution élégante. Malheureusement, je me retrouve avec une solution similaire à M. Leffler, mais j'ajouterai que vous pouvez appeler inutile de l'intérieur d'une fonction Bash pour une meilleure lisibilité:
Tout autre type de redirection de sortie doit être sauvegardé par un fichier temporaire.
la source
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 passons à d'autres choses. Cela nous permettra de savoir si notre format_logs.py a échoué avec une sorte d'exception python.
la source
Capturer et imprimer stderr
Panne
Vous pouvez utiliser
$()
pour capturer stdout, mais vous souhaitez capturer stderr à la place. Vous permutez donc stdout et stderr. Utilisation de fd 3 comme stockage temporaire dans l'algorithme d'échange standard.Si vous voulez capturer ET imprimer, utilisez
tee
pour faire une copie. Dans ce cas, la sortie detee
sera capturée par$()
plutôt que d'aller à la console, mais stderr (oftee
) ira toujours à la console, nous l'utilisons donc comme deuxième sortie pourtee
via le fichier spécial/dev/fd/2
cartee
attend un chemin de fichier plutôt qu'un fd nombre.REMARQUE: c'est énormément de redirections sur une seule ligne et l'ordre est important.
$()
saisit le stdout detee
à la fin du pipeline et le pipeline lui-même achemine stdout de./useless.sh
vers le stdin detee
APRÈS que nous ayons échangé stdin et stdout pour./useless.sh
.Utilisation de stdout de ./useless.sh
L'OP a déclaré qu'il souhaitait toujours utiliser (pas seulement imprimer) stdout, comme
./useless.sh | sed 's/Output/Useless/'
.Pas de problème, faites-le juste AVANT d'échanger stdout et stderr. Je recommande de le déplacer dans une fonction ou un fichier (également-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 des fichiers temporaires car vous
$()
n'en faites qu'un à la fois et cela crée un sous-shell à partir duquel vous ne pouvez pas retourner de variables.la source
En répétant un peu la réponse de Tom Hale, j'ai trouvé qu'il était possible d'envelopper le yoga de redirection dans une fonction pour une réutilisation plus facile. Par exemple:
#!/bin/sh capture () { { captured=$( { { "$@" ; } 1>&3 ; } 2>&1); } 3>&1 } # Example usage; capturing dialog's output without resorting to temp files # was what motivated me to search for this particular SO question capture dialog --menu "Pick one!" 0 0 0 \ "FOO" "Foo" \ "BAR" "Bar" \ "BAZ" "Baz" choice=$captured clear; echo $choice
Il est presque certainement possible de simplifier cela davantage. Je n'ai pas été testé de manière particulièrement approfondie, mais il semble fonctionner à la fois avec bash et ksh.
la source
Si vous souhaitez contourner l'utilisation d'un fichier temporaire, vous pourrez peut-être utiliser la substitution de processus. Je ne l'ai pas encore tout à fait fonctionné. 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
pourtant
Donc, la substitution de processus fait généralement la bonne chose ... malheureusement, chaque fois que j'enveloppe STDIN
>( )
avec quelque chose$()
dans une tentative 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 dans / dev / fd qui appartient au processus parent.La substitution de processus m'a donné la possibilité de travailler avec un flux de données qui n'est plus dans STDERR, malheureusement je ne semble pas être capable de le manipuler comme je le souhaite.
la source
./useless.sh 2> >( ERROR=$( cat <() ); echo "$ERROR" )
vous verriez la sortie deERROR
. Le problème est que la substitution de processus est exécutée dans un sous-shell, donc la valeur définie dans le sous-shell n'affecte pas le shell parent.$ b=$( ( a=$( (echo stdout;echo stderr >&2) ) ) 2>&1 ) $ echo "a=>$a b=>$b" a=>stdout b=>stderr
la source
a=> b=>stderr
a
est évalué et assigné dans un sous-shell, et l'affectation dans le sous-shell n'affecte pas le shell parent. (Testé sur Ubuntu 14.04 LTS ainsi que Mac OS X 10.10.1.)GNU bash, version 4.4.12(1)-release (x86_64-pc-msys)
)SLE 11.4
non plus et produit l'effet décrit par @JonathanLefflerDans zsh:
{ . ./useless.sh > /dev/tty } 2>&1 | read ERROR $ echo $ERROR ( your message )
la source
Pour vérifier les erreurs de vos commandes:
execute () { function="${1}" command="${2}" error=$(eval "${command}" 2>&1 >"/dev/null") if [ ${?} -ne 0 ]; then echo "${function}: ${error}" exit 1 fi }
Inspiré de la fabrication Lean:
la source
if
. Permettez-moi de publier une solution distincte.Une solution simple
{ ERROR=$(./useless.sh 2>&1 1>&$out); } {out}>&1 echo "-" echo $ERROR
Produira:
la source
Améliorer la réponse de YellowApple :
Ceci est une fonction Bash pour capturer stderr dans n'importe quelle variable
stderr_capture_example.sh
:#!/usr/bin/env bash # Capture stderr from a command to a variable while maintaining stdout # @Args: # $1: The variable name to store the stderr output # $2: Vararg command and arguments # @Return: # The Command's Returnn-Code or 2 if missing arguments function capture_stderr { [ $# -lt 2 ] && return 2 local stderr="$1" shift { printf -v "$stderr" '%s' "$({ "$@" 1>&3; } 2>&1)" } 3>&1 } # Testing with a call to erroring ls LANG=C capture_stderr my_stderr ls "$0" '' printf '\nmy_stderr contains:\n%s' "$my_stderr"
Essai:
Production:
Cette fonction peut être utilisée pour capturer le choix retourné d'une
dialog
commande.la source