web-dev-qa-db-fra.com

Comment faire en sorte que STDOUT et STDERR accèdent au terminal et à un fichier journal?

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.)

68
JPLemme

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
108
Paul Tomblin

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 .

13
Jason Sydes

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
3
flolo

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 makes 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
1
denis

Utilisez le programme tee et dup stderr to stdout.

 program 2>&1 | tee > logfile
1
tvanfosson

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.

1
JPLemme

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:

  1. La partie entre {accolades} fonctionnera comme une unité. Son stdout sera écrit directement sur stdout, mais son stderr sera envoyé à la partie droite.

  2. 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.

  3. 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.

0

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
~:
0
gnud