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é.
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.
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.
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
dans votre script, mettez toutes les commandes entre parenthèses, comme ceci:
(
echo start
ls -l
echo end
) | tee foo.log
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).
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.
Bash 4 a une commande coproc
qui établit un tube nommé à une commande et vous permet de communiquer à travers elle.
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.
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).