J'ai un script qui sera exécuté de manière interactive par des utilisateurs non techniques. Le script écrit les mises à jour d'état dans STDOUT afin que l'utilisateur puisse être sûr que le script est en cours d'exécution.
Je veux que STDOUT et STDERR soient tous deux redirigés vers le terminal (afin que l'utilisateur puisse voir que le script fonctionne et voir s'il y a un problème). Je veux aussi que les deux flux soient redirigés vers un fichier journal.
J'ai vu un tas de solutions sur le net. Certains ne fonctionnent pas et d'autres sont horriblement compliqués. J'ai mis au point une solution pratique (à laquelle je vais donner une réponse), mais c'est superficiel.
La solution idéale serait une seule ligne de code qui pourrait être incorporée au début de tout script qui envoie les deux flux au terminal et à un fichier journal.
EDIT: La redirection de STDERR vers STDOUT et le transfert du résultat vers tee fonctionnent, mais cela dépend du fait que les utilisateurs se souviennent de rediriger et diriger la sortie. Je veux que la journalisation soit infaillible et automatique (c'est pourquoi j'aimerais pouvoir intégrer la solution au script lui-même.)
Utilisez "tee" pour rediriger vers un fichier et l'écran. Selon le shell que vous utilisez, vous devez d’abord rediriger stderr vers stdout en utilisant
./a.out 2>&1 | tee output
ou
./a.out |& tee output
Dans csh, il existe une commande intégrée appelée "script" qui capture tout ce qui va à l'écran dans un fichier. Vous commencez en tapant "script", puis en faisant ce que vous voulez capturer, puis en appuyant sur Ctrl-D pour fermer le fichier de script. Je ne connais pas d'équivalent pour sh/bash/ksh.
De plus, puisque vous avez maintenant indiqué que ce sont vos propres scripts sh que vous pouvez modifier, vous pouvez effectuer la redirection en interne en entourant le script entier avec des accolades ou des crochets,
#!/bin/sh
{
... whatever you had in your script before
} 2>&1 | tee output.file
Près d’une demi-décennie plus tard ...
Je crois que c’est la "solution parfaite" recherchée par le PO.
Voici une ligne que vous pouvez ajouter en haut de votre script Bash:
exec > >(tee -a $HOME/logfile) 2>&1
Voici un petit script démontrant son utilisation:
#!/usr/bin/env bash
exec > >(tee -a $HOME/logfile) 2>&1
# Test redirection of STDOUT
echo test_stdout
# Test redirection of STDERR
ls test_stderr___this_file_does_not_exist
(Remarque: cela ne fonctionne qu'avec Bash. Cela fonctionnera avec pas avec/bin/sh.)
Adapté de ici ; D'après ce que je peux dire, l'original n'a pas saisi STDERR dans le fichier journal. Corrigé avec une note de ici .
pour rediriger stderr vers stdout, ajoutez ceci à votre commande: 2>&1
Pour la sortie au terminal et la connexion au fichier, vous devez utiliser tee
Les deux ensemble ressemblent à ceci:
mycommand 2>&1 | tee mylogfile.log
EDIT: Pour intégrer dans votre script, vous feriez la même chose. Alors ton script
#!/bin/sh
whatever1
whatever2
...
whatever3
finirait comme
#!/bin/sh
( whatever1
whatever2
...
whatever3 ) 2>&1 | tee mylogfile.log
Un an plus tard, voici un vieux script bash pour tout enregistrer. Par exemple,teelog make ...
se connecte à un nom de journal généré (et voyez aussi l'astuce pour consigner les make
s imbriquées.)
#!/bin/bash
me=teelog
Version="2008-10-9 oct denis-bz"
Help() {
cat <<!
$me anycommand args ...
logs the output of "anycommand ..." as well as displaying it on the screen,
by running
anycommand args ... 2>&1 | tee `day`-command-args.log
That is, stdout and stderr go to both the screen, and to a log file.
(The Unix "tee" command is named after "T" pipe fittings, 1 in -> 2 out;
see http://en.wikipedia.org/wiki/Tee_(command) ).
The default log file name is made up from "command" and all the "args":
$me cmd -opt dir/file logs to `day`-cmd--opt-file.log .
To log to xx.log instead, either export log=xx.log or
$me log=xx.log cmd ...
If "logdir" is set, logs are put in that directory, which must exist.
An old xx.log is moved to /tmp/\$USER-xx.log .
The log file has a header like
# from: command args ...
# run: date pwd etc.
to show what was run; see "From" in this file.
Called as "Log" (ln -s $me Log), Log anycommand ... logs to a file:
command args ... > `day`-command-args.log
and tees stderr to both the log file and the terminal -- bash only.
Some commands that Prompt for input from the console, such as a password,
don't Prompt if they "| tee"; you can only type ahead, carefully.
To log all "make" s, including nested ones like
cd dir1; \$(MAKE)
cd dir2; \$(MAKE)
...
export MAKE="$me make"
!
# See also: output logging in screen(1).
exit 1
}
#-------------------------------------------------------------------------------
# bzutil.sh denisbz may2008 --
day() { # 30mar, 3mar
/bin/date +%e%h | tr '[A-Z]' '[a-z]' | tr -d ' '
}
edate() { # 19 May 2008 15:56
echo `/bin/date "+%e %h %Y %H:%M"`
}
From() { # header # from: $* # run: date pwd ...
case `uname` in Darwin )
mac=" mac `sw_vers -productVersion`"
esac
cut -c -200 <<!
${comment-#} from: $@
${comment-#} run: `edate` in $PWD `uname -n` $mac `Arch`
!
# mac $PWD is pwd -L not -P real
}
# log name: day-args*.log, change this if you like --
logfilename() {
log=`day`
[[ $1 == "Sudo" ]] && shift
for arg
do
log="$log-${arg##*/}" # basename
(( ${#log} >= 100 )) && break # max len 100
done
# no blanks etc in logfilename please, tr them to "-"
echo $logdir/` echo "$log".log | tr -C '.:+=[:alnum:]_\n' - `
}
#-------------------------------------------------------------------------------
case "$1" in
-v* | --v* )
echo "$0 version: $Version"
exit 1 ;;
"" | -* )
Help
esac
# scan log= etc --
while [[ $1 == [a-zA-Z_]*=* ]]; do
export "$1"
shift
done
: ${logdir=.}
[[ -w $logdir ]] || {
echo >&2 "error: $me: can't write in logdir $logdir"
exit 1
}
: ${log=` logfilename "$@" `}
[[ -f $log ]] &&
/bin/mv "$log" "/tmp/$USER-${log##*/}"
case ${0##*/} in # basename
log | Log ) # both to log, stderr to caller's stderr too --
{
From "$@"
"$@"
} > $log 2> >(tee /dev/stderr) # bash only
# see http://wooledge.org:8000/BashFAQ 47, stderr to a pipe
;;
* )
#-------------------------------------------------------------------------------
{
From "$@" # header: from ... date pwd etc.
"$@" 2>&1 # run the cmd with stderr and stdout both to the log
} | tee $log
# mac tee buffers stdout ?
esac
Utilisez le programme tee et dup stderr to stdout.
program 2>&1 | tee > logfile
J'ai créé un script appelé "RunScript.sh". Le contenu de ce script est:
${APP_HOME}/${1}.sh ${2} ${3} ${4} ${5} ${6} 2>&1 | tee -a ${APP_HOME}/${1}.log
Je l'appelle comme ça:
./RunScript.sh ScriptToRun Param1 Param2 Param3 ...
Cela fonctionne, mais cela nécessite que les scripts de l'application soient exécutés via un script externe. C'est un peu kludgy.
Cela fait l'affaire et préserve également la distinction entre stdout et stderr:
{ the_cmd > >(tee stdout.txt ); } 2> >(tee stderr.txt >&2 )
Voici un script:
the_cmd()
{
echo out;
1>&2 echo err;
}
{ the_cmd > >(tee stdout.txt ); } 2> >(tee stderr.txt >&2 )
Voici une session:
$ foo=$(./example.sh)
err
$ echo $foo
out
$ cat stdout.txt
out
$ cat stderr.txt
err
Voici comment ça fonctionne:
La partie entre {accolades} fonctionnera comme une unité. Son stdout sera écrit directement sur stdout, mais son stderr sera envoyé à la partie droite.
Tout d’abord, la sortie standard de the_cmd
est envoyée à tee
, qui le relaie à la sortie standard, mais l’enregistre également dans un fichier.
Ensuite, le stderr de l'étape 2 est envoyé à tee
, qui le relaie à stdout. Nous devons donc envoyer explicitement stdout pour la deuxième commande à stderr.
Utilisez la commande script
dans votre script (script man 1)
Créez un shell script (2 lignes) qui configure script () puis appelle exit.
Partie 1: wrap.sh
#!/bin/sh
script -c './realscript.sh'
exit
Partie 2: realscript.sh
#!/bin/sh
echo 'Output'
Résultat:
~: sh wrap.sh
Script started, file is TypeScript
Output
Script done, file is TypeScript
~: cat TypeScript
Script started on fr. 12. des. 2008 kl. 18.07 +0100
Output
Script done on fr. 12. des. 2008 kl. 18.07 +0100
~: