Comment rediriger la sortie d'un script shell entier dans le script lui-même?

205

Est-il possible de rediriger toute la sortie d'un script shell Bourne vers quelque part, mais avec des commandes shell à l'intérieur du script lui-même?

Rediriger la sortie d'une seule commande est facile, mais je veux quelque chose de plus comme ceci:

#!/bin/sh
if [ ! -t 0 ]; then
    # redirect all of my output to a file here
fi

# rest of script...

Signification: si le script est exécuté de manière non interactive (par exemple, cron), enregistrez la sortie de tout dans un fichier. S'il est exécuté de manière interactive à partir d'un shell, laissez la sortie aller à stdout comme d'habitude.

Je veux le faire pour un script normalement exécuté par l'utilitaire périodique FreeBSD. Cela fait partie de la course quotidienne, que je ne tiens normalement pas à voir tous les jours par e-mail, donc je ne l'ai pas envoyé. Cependant, si quelque chose à l'intérieur de ce script particulier échoue, c'est important pour moi et j'aimerais pouvoir capturer et envoyer par courrier électronique la sortie de cette partie des tâches quotidiennes.

Mise à jour: la réponse de Joshua est parfaite, mais je voulais également enregistrer et restaurer stdout et stderr autour de l'ensemble du script, ce qui se fait comme suit:

# save stdout and stderr to file descriptors 3 and 4, then redirect them to "foo"
exec 3>&1 4>&2 >foo 2>&1

# ...

# restore stdout and stderr
exec 1>&3 2>&4
Steve Madsen
la source
2
Tester $ TERM n'est pas le meilleur moyen de tester le mode interactif. Au lieu de cela, testez si stdin est un tty (test -t 0).
Chris Jester-Young
2
En d'autres termes: si [! -t 0]; puis exec> unfichier 2> & 1; fi
Chris Jester-Young
1
Voir ici pour toute la bonté: http://tldp.org/LDP/abs/html/io-redirection.html Fondamentalement, ce qui a été dit par Joshua. Le fichier exec> redirige stdout vers un fichier spécifique, le fichier exec <remplace stdin par fichier, etc. C'est le même que d'habitude mais en utilisant exec (voir man exec pour plus de détails).
Loki
Dans votre section de mise à jour, vous devez également fermer les FD 3 et 4, comme ceci: exec 1>&3 2>&4 3>&- 4>&-
Gurjeet Singh

Réponses:

173

Répondre à la question telle que mise à jour.

#...part of script without redirection...

{
    #...part of script with redirection...
} > file1 2>file2 # ...and others as appropriate...

#...residue of script without redirection...

Les accolades '{...}' fournissent une unité de redirection d'E / S. Les accolades doivent apparaître là où une commande pourrait apparaître - de manière simplifiée, au début d'une ligne ou après un point-virgule. ( Oui, cela peut être rendu plus précis; si vous voulez ergoter, faites le moi savoir. )

Vous avez raison de pouvoir conserver la stdout et la stderr d'origine avec les redirections que vous avez montrées, mais il est généralement plus simple pour les personnes qui doivent maintenir le script plus tard de comprendre ce qui se passe si vous définissez le code redirigé comme indiqué ci-dessus.

Les sections pertinentes du manuel Bash sont Regroupement des commandes et Réacheminement E / S . Les sections pertinentes de la spécification POSIX sont des commandes composées et I / O redirection . Bash a quelques notations supplémentaires, mais est par ailleurs similaire à la spécification du shell POSIX.

Jonathan Leffler
la source
3
C'est beaucoup plus clair que d'enregistrer les descripteurs d'origine et de les restaurer plus tard.
Steve Madsen
23
J'ai dû faire une recherche sur Google pour comprendre ce que cela faisait vraiment, alors je voulais partager. Les accolades deviennent un "bloc de code" , qui, en effet, crée une fonction anonyme . Tout le résultat dans le bloc de code peut ensuite être redirigé (voir l' exemple 3-2 à partir de ce lien). Notez également que les accolades ne lancent pas de sous - shell , mais des redirections d'E / S similaires peuvent être effectuées avec des sous-shell à l'aide de parenthèses.
chris
3
J'aime mieux cette solution que les autres. Même une personne n'ayant que la compréhension la plus élémentaire de la redirection d'E / S peut comprendre ce qui se passe. De plus, c'est plus verbeux. Et, en tant que Pythoner, j'aime verbeux.
John Red
Mieux vaut faire >>. Certaines personnes en ont l'habitude >. L'ajout est toujours plus sûr et plus recommandé que l'écrasement. Quelqu'un a écrit une application qui utilise la commande de copie standard pour exporter certaines données vers la même destination.
neverMind9
Cette application est ccc.bmw71 (.pro) (3C Battery Monitor Widget, anciennement «Battery Monitor Widget») exporte toujours l'historique de la batterie vers /sdcard/bmw_history.txt. S'il existe déjà, devinez quoi, ÉCRASER! Cela m'a fait accidentellement perdre un peu d'histoire de la batterie. J'ai fixé la limite en jours de 30 à un nombre très élevé, ce qui l'a invalidée et l'a remise à 30. Je voulais exporter l'historique actuel de la batterie avant d'importer la sauvegarde existante. Et c'est arrivé.
neverMind9
176

En règle générale, nous plaçons l'un d'entre eux en haut ou près du haut du script. Les scripts qui analysent leurs lignes de commande feraient la redirection après l'analyse.

Envoyer stdout dans un fichier

exec > file

avec stderr

exec > file                                                                      
exec 2>&1

ajouter à la fois stdout et stderr au fichier

exec >> file
exec 2>&1

Comme Jonathan Leffler l'a mentionné dans son commentaire :

execa deux emplois distincts. La première consiste à remplacer le shell (script) en cours d'exécution par un nouveau programme. L'autre modifie les redirections d'E / S dans le shell actuel. Cela se distingue par l'absence d'argument exec.

Joshua
la source
7
Je dis aussi ajouter 2> & 1 à la fin de cela, juste pour que stderr se fasse attraper aussi. :-)
Chris Jester-Young
7
Où les mettez-vous? En haut du script?
colan
4
Avec cette solution, il faut également réinitialiser la redirection de sortie du script. La prochaine réponse de Jonathan Leffler est plus «infaillible» dans ce sens.
Chuim
6
@JohnRed: execa deux tâches distinctes. L'une consiste à remplacer le script actuel par une autre commande, en utilisant le même processus - vous spécifiez l'autre commande comme argument exec(et vous pouvez modifier les redirections d'E / S au fur et à mesure). L'autre tâche consiste à modifier les redirections d'E / S dans le script shell actuel sans le remplacer. Cette notation se distingue par le fait de ne pas avoir de commande comme argument pour exec. La notation dans cette réponse est de la variante "E / S uniquement" - elle modifie uniquement la redirection et ne remplace pas le script en cours d'exécution. (La setcommande est également polyvalente.)
Jonathan Leffler
18
exec > >(tee -a "logs/logdata.log") 2>&1imprime les journaux sur l'écran ainsi que les écrit dans un fichier
shriyog
32

Vous pouvez faire du script entier une fonction comme celle-ci:

main_function() {
  do_things_here
}

puis à la fin du script:

if [ -z $TERM ]; then
  # if not run via terminal, log everything into a log file
  main_function 2>&1 >> /var/log/my_uber_script.log
else
  # run via terminal, only output to screen
  main_function
fi

Alternativement, vous pouvez tout enregistrer dans le fichier journal à chaque exécution et le restituer à stdout en faisant simplement:

# log everything, but also output to stdout
main_function 2>&1 | tee -a /var/log/my_uber_script.log
dbguy
la source
Voulez-vous dire main_function >> /var/log/my_uber_script.log 2> & 1
Felipe Alvarez
J'aime utiliser main_function dans un tel tuyau. Mais dans ce cas, votre script ne renvoie pas la valeur de retour d'origine. Dans le cas bash, vous devez quitter puis utiliser 'exit $ {PIPESTATUS [0]}'.
rudimeier
7

Pour enregistrer la stdout et la stderr d'origine, vous pouvez utiliser:

exec [fd number]<&1 
exec [fd number]<&2

Par exemple, le code suivant imprimera "walla1" et "walla2" dans le fichier journal ( a.txt), "walla3" vers stdout, "walla4" vers stderr.

#!/bin/bash

exec 5<&1
exec 6<&2

exec 1> ~/a.txt 2>&1

echo "walla1"
echo "walla2" >&2
echo "walla3" >&5
echo "walla4" >&6
Eyal leshem
la source
1
Normalement, il serait préférable d'utiliser exec 5>&1et d' exec 6>&2utiliser la notation de redirection de sortie plutôt que la notation de redirection d'entrée pour les sorties. Vous vous en sortez parce que lorsque le script est exécuté à partir d'un terminal, l'entrée standard est également accessible en écriture et la sortie standard et l'erreur standard sont lisibles en vertu (ou est-ce le «vice»?) D'une bizarrerie historique: le terminal est ouvert pour en lecture et en écriture et la même description de fichier ouvert est utilisée pour les trois descripteurs de fichier d'E / S standard.
Jonathan Leffler
3
[ -t <&0 ] || exec >> test.log
Dimitar
la source