Je sais comment rediriger stdout vers un fichier:
exec > foo.log
echo test
cela mettra le "test" dans le fichier foo.log.
Maintenant, je veux rediriger la sortie dans le fichier journal ET le garder sur stdout
c'est-à-dire que cela peut être fait trivialement en dehors du script:
script | tee foo.log
mais je veux faire le déclarer dans le script lui-même
J'ai essayé
exec | tee foo.log
mais ça n'a pas marché.
#!/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 ceci est bash
, pas sh
. Si vous appelez le script avec sh myscript.sh
, vous obtiendrez une erreur du type syntax error near unexpected token '>'
.
Si vous travaillez avec des interruptions de signal, vous pouvez utiliser l'option tee -i
pour éviter toute perturbation de la sortie si un signal se produisait. (Merci à JamesThomasMoon1979 pour le commentaire.)
Les outils qui modifient leur sortie en fonction de leur écriture dans un canal ou un terminal (ls
à l'aide de couleurs et d'une sortie en colonnes, par exemple) détecteront la construction ci-dessus comme signifiant qu'ils s'affichent dans un canal.
Il existe des options pour appliquer la coloration/mise en colonnes (par exemple ls -C --color=always
). Notez que les codes de couleur seront également é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 séparé. Cela signifie
./script.sh >/dev/null
n’émettra pas bar
sur le terminal, mais uniquement sur le fichier journal, et
./script.sh 2>/dev/null
émettra à la fois foo
et bar
sur le terminal. Clairement, ce n'est pas le comportement qu'un utilisateur normal est susceptible de s'attendre. Ce problème peut être résolu en utilisant deux processus tee distincts, tous deux ajoutés au 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 journal. Si vous souhaitez ce comportement, vous devez ajouter
>foo.log
en haut du script.)
La spécification POSIX.1-2008 de tee(1)
nécessite que la sortie ne soit pas tamponnée, c'est-à-dire qu'elle ne soit même pas mise en mémoire tampon, il est donc possible que STDOUT et STDERR se retrouvent sur le même ligne de foo.log
; Toutefois, cela pourrait également se produire sur le terminal. Le fichier journal reflétera fidèlement ce que pourrait afficher sur le terminal, si ce n’est un miroir exact. de cela. Si vous souhaitez que les lignes STDOUT soient bien séparées des lignes STDERR, envisagez d'utiliser deux fichiers journaux, avec éventuellement des préfixes d'horodatage sur chaque ligne pour permettre un réassemblage chronologique ultérieurement.
Solution pour busybox, shells macOS bash et non bash
La réponse acceptée 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 correctement exec >$PIPE
, essayant de créer un fichier ordinaire portant le même nom que le canal nommé, qui échoue et se bloque.
Espérons que cela serait utile à quelqu'un d'autre qui n'a pas de bash.
De même, pour toute personne utilisant un canal nommé, rm $PIPE
est sécurisé, car cela dissocie le canal du système de fichiers VFS, mais les processus qui l'utilisent conservent toujours une référence jusqu'à ce qu'ils soient terminés.
Notez que l'utilisation de $ * n'est pas nécessairement sûre.
#!/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 with stdout redirected to the named pipe
SELF_LOGGING=1 sh $0 $* >$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, which is 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 fichier de script, placez toutes les commandes entre parenthèses, comme suit:
(
echo start
ls -l
echo end
) | tee foo.log
Un moyen facile de créer un journal de script bash dans syslog. La sortie du script est disponible à la fois via /var/log/syslog
et via stderr. syslog ajoutera des métadonnées utiles, y compris des horodatages.
Ajoutez cette ligne en haut:
exec &> >(logger -t myscript -s)
Vous pouvez également 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 horodatages).
En utilisant la réponse acceptée, mon script renvoyait exceptionnellement tôt (juste après 'exec>> (tee ...)'), laissant le reste de mon script s'exécuter en arrière-plan. N'ayant pas réussi à obtenir cette solution, j'ai trouvé une autre solution au 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 du script du processus, en passant par le canal, dans le sous-processus en arrière-plan de 'tee' qui enregistre tout sur un disque et sur la sortie standard du script.
Notez que 'exec &>' redirige à la fois stdout et stderr, nous pourrions les rediriger séparément si nous le souhaitons, ou passer à 'exec>' si nous voulons simplement stdout.
Même si le tuyau est supprimé du système de fichiers au début du script, il continuera à fonctionner jusqu'à la fin du processus. Nous ne pouvons simplement pas le référencer en utilisant le nom du fichier après la ligne de commande.
Bash 4 a une commande coproc
qui établit un canal nommé vers une commande et vous permet de communiquer à travers elle.
Je ne peux pas dire que je suis à l'aise avec l'une des solutions basées sur exec. Je préfère utiliser le tee directement, alors je demande au script de s'appeler lui-même avec le tee:
# 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) $0 $@ >> log.txt
TEE=false $0 $@ 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. make tee = false la valeur par défaut à la place, demandez à TEE de conserver le fichier journal à la place, etc. Je suppose que cette solution est similaire à celle de jbarlow, mais qu'elle est plus simple, peut-être que la mienne a des limitations que je n'ai pas encore rencontrées.