rediriger la copie de stdout vers le fichier journal à partir du script bash lui-même

je sais rediriger stdout pour un fichier:

exec > foo.log
echo test

cela mettra le "test" dans le foo.le fichier de log.

maintenant je veux rediriger la sortie dans le fichier journal et le garder sur stdout

c'est à dire qu'il peut être fait trivialement à partir de l'extérieur du script:

script | tee foo.log

mais je veux le déclarer dans le script lui-même

j'ai essayé

exec | tee foo.log

mais ça n'a pas marché.

209
demandé sur Felix 2010-07-04 03:04:14

9 réponses

#!/usr/bin/env bash

# Redirect stdout ( > ) into a named pipe ( >() ) running "tee"
exec > >(tee -i logfile.txt)

# Without this, only stdout would be captured - i.e. your
# log file would not contain any error messages.
# SEE (and upvote) the answer by Adam Spiers, which keeps STDERR
# as a separate stream - I did not want to steal from him by simply
# adding his answer to mine.
exec 2>&1

echo "foo"
echo "bar" >&2

Notez que c'est bash , pas sh . Si vous invoquez le script avec sh myscript.sh , vous obtiendrez une erreur du type syntax error near unexpected token '>' .

si vous travaillez avec des pièges à Signaux, vous pouvez utiliser l'option tee -i pour éviter de perturber la sortie si un signal se produit. (Merci à JamesThomasMoon1979 pour le commentaire.)


outils qui changent leur sortie en fonction de qu'ils écrivent à un tuyau ou un terminal ( ls en utilisant les couleurs et la sortie en colonne, par exemple) détectera la construction ci-dessus comme signifiant qu'ils produisent à un tuyau.

il y a des options pour appliquer la colorisation / en colonne (par exemple ls -C --color=always ). Notez que cela aura pour conséquence que les codes de couleur seront aussi écrits dans le fichier journal, ce qui le rendra moins lisible.

262
répondu DevSolar 2017-03-08 13:49:54

la réponse acceptée ne conserve pas STDERR en tant que descripteur de fichier distinct. Ça veut dire

./script.sh >/dev/null

ne sortira pas bar vers le terminal, seulement vers le fichier journal, et

./script.sh 2>/dev/null

affichera à la fois foo et bar au terminal. Clairement ce n'est pas le comportement d'un utilisateur normal est susceptible de s'attendre. Cela peut être fixé par l'utilisation de deux processus tee séparés les deux en ajoutant à la même fichier journal:

#!/bin/bash

# See (and upvote) the comment by JamesThomasMoon1979 
# explaining the use of the -i option to tee.
exec >  >(tee -ia foo.log)
exec 2> >(tee -ia foo.log >&2)

echo "foo"
echo "bar" >&2

(notez que ce qui précède ne tronque pas initialement le fichier log - si vous voulez ce comportement, vous devez ajouter

>foo.log

en haut du script.)

the POSIX.1-2008 spécification de tee(1) exige que la sortie est non tamponnée, c'est-à-dire pas même ligne-tampon, dans ce cas, il est possible que STDOUT et STDERR pourrait finir sur la même ligne de foo.log ; cependant cela arrive aussi sur le terminal, de sorte que le fichier journal sera un fidèle reflet de ce que pourrait être vu sur le terminal, si ce n'est un miroir exact. Si vous voulez que les lignes STDOUT soient clairement séparées des lignes STDERR, envisagez d'utiliser deux fichiers journaux, éventuellement avec des préfixes de date sur chaque ligne pour permettre le réassemblage chronologique plus tard.

151
répondu Adam Spiers 2016-09-02 15:21:34

Solution de busybox et non-bash coquilles

La accepté réponse est certainement le meilleur choix pour bash. Je travaille dans un environnement Busybox sans accès à bash, et il ne comprend pas la syntaxe exec > >(tee log.txt) . Il ne fait pas non plus exec >$PIPE correctement, en essayant de créer un fichier ordinaire avec le même nom que le tuyau nommé, qui échoue et pend.

espérons que ce soit utile à quelqu'un d'autre qui n'a pas de bash.

aussi, pour n'importe qui utilisant un tuyau nommé, il est sûr à rm $PIPE , parce que cela déverrouille le tuyau de la VFS, mais les processus qui l'utilisent maintiennent toujours une référence compter dessus jusqu'à ce qu'ils soient finis.

Remarque l'utilisation de $* n'est pas nécessairement sans danger.

#!/bin/sh

if [ "$SELF_LOGGING" != "1" ]
then
    # The parent process will enter this branch and set up logging

    # Create a named piped for logging the child's output
    PIPE=tmp.fifo
    mkfifo $PIPE

    # Launch the child process without redirected to the named pipe
    SELF_LOGGING=1 sh "151900920" $* >$PIPE &

    # Save PID of child process
    PID=$!

    # Launch tee in a separate process
    tee logfile <$PIPE &

    # Unlink $PIPE because the parent process no longer needs it
    rm $PIPE    

    # Wait for child process running the rest of this script
    wait $PID

    # Return the error code from the child process
    exit $?
fi

# The rest of the script goes here
23
répondu jbarlow 2016-04-13 22:07:39

dans votre script, mettez toutes les commandes entre parenthèses, comme ceci:

(
echo start
ls -l
echo end
) | tee foo.log
18
répondu WReach 2010-07-03 23:48:03

façon facile de faire un log de script bash vers syslog. La sortie du script est disponible à la fois par /var/log/syslog et par stderr. syslog ajoutera des métadonnées utiles, y compris des horodateurs.

Ajouter cette ligne en haut:

exec &> >(logger -t myscript -s)

alternativement, envoyer le journal dans un fichier séparé:

exec &> >(ts |tee -a /tmp/myscript.output >&2 )

cela nécessite moreutils (pour la commande ts , qui ajoute des horodateurs).

11
répondu Tobu 2014-12-07 18:22:19

en utilisant la réponse acceptée mon script revenait exceptionnellement tôt (juste après ' exec > >(tee ...) ') laissant le reste de mon script en arrière plan. Comme je n'arrivais pas à faire fonctionner cette solution à ma façon, j'ai trouvé une autre solution/travailler autour du problème:

# Logging setup
logfile=mylogfile
mkfifo ${logfile}.pipe
tee < ${logfile}.pipe $logfile &
exec &> ${logfile}.pipe
rm ${logfile}.pipe

# Rest of my script

cela rend la sortie de script aller du processus, à travers la pipe dans le processus de fond de 'tee' qui enregistre tout à disque et à l'original stdout du script.

notez que 'exec & >' redirige à la fois stdout et stderr, nous pourrions les rediriger séparément si nous le voulons, ou changer en 'exec >' si nous voulons simplement stdout.

même si la pipe est retirée du système de fichiers au début du script, elle continuera à fonctionner jusqu'à la fin des processus. On ne peut pas le référencer en utilisant le nom du fichier après la ligne rm.

9
répondu fgunger 2011-07-09 14:01:12

Bash 4 a une commande coproc qui établit un tube nommé à une commande et vous permet de communiquer à travers elle.

1
répondu Dennis Williamson 2010-07-04 01:15:24

ne peut pas dire que je suis à l'aise avec aucune des solutions basées sur exec. Je préfère utiliser tee directement, donc je fais le script s'appeler avec tee quand demandé:

# my script: 

check_tee_output()
{
    # copy (append) stdout and stderr to log file if TEE is unset or true
    if [[ -z $TEE || "$TEE" == true ]]; then 
        echo '-------------------------------------------' >> log.txt
        echo '***' $(date) "151900920" $@ >> log.txt
        TEE=false "151900920" $@ 2>&1 | tee --append log.txt
        exit $?
    fi 
}

check_tee_output $@

rest of my script

cela vous permet de faire ceci:

your_script.sh args           # tee 
TEE=true your_script.sh args  # tee 
TEE=false your_script.sh args # don't tee
export TEE=false
your_script.sh args           # tee

vous pouvez personnaliser cela, par exemple faire tee=false par défaut à la place, faire TEE tenir le fichier journal à la place, etc. Je suppose que cette solution est similaire à celle de jbarlow, mais plus simple, peut-être que la mienne a des limites que je ne suis pas venu partout encore.

0
répondu Oliver 2018-07-26 22:44:37

ni l'un ni l'autre de ceux-ci est une solution parfaite, mais voici quelques choses que vous pourriez essayer:

exec >foo.log
tail -f foo.log &
# rest of your script

ou

PIPE=tmp.fifo
mkfifo $PIPE
exec >$PIPE
tee foo.log <$PIPE &
# rest of your script
rm $PIPE

le second laisserait un fichier pipe en attente si quelque chose ne va pas avec votre script, ce qui peut être ou non un problème (i.e. peut-être que vous pourriez rm dans l'interpréteur de commandes par la suite).

-1
répondu David Z 2010-07-03 23:45:14