web-dev-qa-db-fra.com

Capturez la sortie standard dans une variable mais affichez-la toujours dans la console

J'ai un script bash qui appelle plusieurs processus de longue durée. Je veux capturer la sortie de ces appels dans des variables pour des raisons de traitement. Cependant, comme il s'agit de processus longs, j'aimerais que la sortie des appels rsync soit affichée dans la console en en temps réel et non après la fait.

À cette fin, j'ai trouvé une façon de le faire, mais cela repose sur la sortie du texte vers/dev/stderr. Je pense que la sortie vers/dev/stderr n'est pas une bonne façon de faire les choses.

VAR1=$(for i in {1..5}; do sleep 1; echo $i; done | tee /dev/stderr)

VAR2=$(rsync -r -t --out-format='%n%L' --delete -s /path/source1/ /path/target1 | tee /dev/stderr)

VAR3=$(rsync -r -t --out-format='%n%L' --delete -s /path/source2/ /path/target2 | tee /dev/stderr)

Dans l'exemple ci-dessus, j'appelle rsync plusieurs fois et je veux voir les noms de fichiers au fur et à mesure qu'ils sont traités, mais à la fin je veux toujours la sortie dans une variable car je vais l'analyser plus tard.

Existe-t-il une manière "plus propre" d'accomplir cela?

Si cela fait une différence, j'utilise Ubuntu 12.04, bash 4.2.24.

65
Mendhak

Dupliquez & 1 dans votre shell (dans mon exemple à 5) et utilisez & 5 dans le sous-shell (pour que vous écriviez sur stdout (& 1) du shell parent):

exec 5>&1
FF=$(echo aaa|tee >(cat - >&5))
echo $FF

Imprime aaa deux fois, celles à cause de l'écho dans le sous-shell, et la deuxième fois imprime la valeur de la variable.

Dans votre code:

exec 5>&1
VAR1=$(for i in {1..5}; do sleep 1; echo $i; done | tee >(cat - >&5))
# use the value of VAR1
73
Op De Cirkel

La réponse de l'Op De Cirkel a la bonne idée. Cela peut être encore plus simplifié (en évitant d'utiliser cat):

exec 5>&1
FF=$(echo aaa|tee /dev/fd/5)
echo $FF
34
Russell Davis

Voici un exemple capturant à la fois stderr et le code de sortie de la commande. Cela s'appuie sur la réponse de Russell Davis.

exec 5>&1
FF=$(ls /taco/ 2>&1 |tee /dev/fd/5; exit ${PIPESTATUS[0]})
exit_code=$?
echo "$FF"
echo "Exit Code: $exit_code"

Si le dossier /taco/ existe, cela capturera son contenu. Si le dossier n'existe pas, il capturera un message d'erreur et le code de sortie sera 2.

Si vous omettez 2>&1alors seulement stdout sera capturé.

12
Bryan Roach

Vous pouvez utiliser plus de trois descripteurs de fichiers. Essayez ici:

http://tldp.org/LDP/abs/html/io-redirection.html

"Chaque fichier ouvert se voit attribuer un descripteur de fichier. [2] Les descripteurs de fichier pour stdin, stdout et stderr sont respectivement 0, 1 et 2. Pour ouvrir des fichiers supplémentaires, il reste des descripteurs 3 à 9. Il est parfois utile de attribuer l'un de ces descripteurs de fichiers supplémentaires à stdin, stdout ou stderr en tant que lien en double temporaire. "

Le point est de savoir s'il vaut la peine de rendre le script plus compliqué juste pour atteindre ce résultat. En fait, ce n'est pas vraiment mal, comme vous le faites.

5
Piotr Wadas

Si par "la console" vous entendez votre ATS actuel, essayez

variable=$(command with options | tee /dev/tty)

C'est une pratique légèrement douteuse car les gens qui essaient de l'utiliser parfois sont surpris lorsque la sortie atterrit dans un endroit inattendu quand ils n'ont pas de TTY (tâches cron, etc.).

5
tripleee

Alternative à l'utilisation de /dev/tty, ou un descripteur de fichier supplémentaire comme suggéré par les autres réponses, vous pouvez également le retourner et simplement utiliser un fichier temporaire. C'est sans doute plus facile à lire et plus portable dans certaines situations.

tmpFile=$(mktemp)  # mak-a de temp
rsync /a /b | tee $tmpFile # sync my b*tch up
if grep "U F'd up" $tmpFile; then
  rm -rf / #Seppuku
fi
0
leondepeon