web-dev-qa-db-fra.com

Comment ajouter un horodatage à la redirection STDERR

Dans bash/ksh, pouvons-nous ajouter un horodatage à la redirection STDERR?

Par exemple. myscript.sh 2> error.log

Je veux aussi avoir un horodatage sur le journal.

32
est

Si vous parlez d'un horodatage à jour sur chaque ligne, vous voudrez probablement le faire dans votre script actuel (mais voir ci-dessous pour une solution intéressante si vous n'avez pas le pouvoir de le modifier). Si vous voulez juste une date de marqueur sur sa propre ligne avant que votre script ne commence à écrire, utilisez:

( date 1>&2 ; myscript.sh ) 2>error.log

Ce qu'il vous faut, c'est une astuce pour diriger stderr vers un autre programme permettant d'ajouter des horodatages à chaque ligne. Vous pourriez faire cela avec un programme C mais il existe un moyen beaucoup plus sournois qui utilise simplement bash.

Commencez par créer un script qui ajoutera l’horodatage à chaque ligne (appelé predate.sh):

#!/bin/bash
while read line ; do
    echo "$(date): ${line}"
done

Par exemple:

( echo a ; sleep 5 ; echo b ; sleep 2 ; echo c ) | ./predate.sh

produit:

Fri Oct  2 12:31:39 WAST 2009: a
Fri Oct  2 12:31:44 WAST 2009: b
Fri Oct  2 12:31:46 WAST 2009: c

Alors vous avez besoin de un autre astuce qui peut échanger stdout et stderr, cette petite monstruosité ici:

( myscript.sh 3>&1 1>&2- 2>&3- )

Ensuite, il est simple de combiner les deux astuces en horodatant stdout et en le redirigeant vers votre fichier:

( myscript.sh 3>&1 1>&2- 2>&3- ) | ./predate.sh >error.log

La transcription suivante montre cela en action:

pax> cat predate.sh
    #!/bin/bash
    while read line ; do
        echo "$(date): ${line}"
    done
pax> cat tstdate.sh
    #!/bin/bash
    echo a to stderr then wait five seconds 1>&2
    sleep 5
    echo b to stderr then wait two seconds 1>&2
    sleep 2
    echo c to stderr 1>&2
    echo d to stdout
pax> ( ( ./tstdate.sh ) 3>&1 1>&2- 2>&3- ) | ./predate.sh >error.log
    d to stdout
pax> cat error.log
    Fri Oct  2 12:49:40 WAST 2009: a to stderr then wait five seconds
    Fri Oct  2 12:49:45 WAST 2009: b to stderr then wait two seconds
    Fri Oct  2 12:49:47 WAST 2009: c to stderr

Comme déjà mentionné, predate.sh préfixe chaque ligne avec un horodatage et le tstdate.sh est simplement un programme de test permettant d'écrire dans stdout et stderr avec des intervalles de temps spécifiques.

Lorsque vous exécutez la commande, vous obtenez réellement "d to stdout" écrit dans stderr (mais c’est votre périphérique TTY ou tout ce que stdout a pu être lors du démarrage). Les lignes horodatées stderr sont écrites dans le fichier souhaité.

27
paxdiablo

Le paquet devscripts de Debian/Ubuntu contient un script appelé annotate-output qui le fait (pour stdout et stderr).

$ annotate-output make
21:41:21 I: Started make
21:41:21 O: gcc -Wall program.c
21:43:18 E: program.c: Couldn't compile, and took me ages to find out
21:43:19 E: collect2: ld returned 1 exit status
21:43:19 E: make: *** [all] Error 1
21:43:19 I: Finished with exitcode 2
25
Jukka Matilainen

Voici une version qui utilise une boucle while read telle que pax's , mais ne nécessite pas de descripteur de fichier supplémentaire ni de script séparé (bien que vous puissiez en utiliser un). Il utilise la substitution de processus:

myscript.sh 2> >( while read line; do echo "$(date): ${line}"; done > error.log )

Utiliser pax's predate.sh:

myscript.sh 2> >( predate.sh > error.log )
15

Le programme ts de moreutils package canalise l’entrée standard vers la sortie standard et préfixe chaque ligne d’un horodatage.

Pour préfixer les lignes stdout: command | ts
Pour préfixer stdout et stderr: command 2>&1 | ts 

12
Vladimir Panteleev

J'aime ces scripts Shell portables, mais un peu dérangés par le fait qu'ils fourchent/exec date (1) pour chaque ligne. Voici un guide rapide Perl one-liner pour faire la même chose plus efficacement:

Perl -p -MPOSIX -e 'BEGIN {$!=1} $_ = strftime("%T ", localtime) . $_'

Pour l'utiliser, il suffit d'alimenter cette entrée de commande via son stdin:

(echo hi; sleep 1; echo lo) | Perl -p -MPOSIX -e 'BEGIN {$|=1} $_ = strftime("%T ", localtime) . $_'
4
Amos Shapira

Plutôt que d'écrire un script vers lequel diriger, je préfère écrire le logger en tant que fonction à l'intérieur du script, puis lui envoyer l'intégralité du processus avec des crochets, comme ceci:

 # Vars    
 logfile=/path/to/scriptoutput.log

 # Defined functions
 teelogger(){
   log=$1
   while read line ; do
     print "$(date +"%x %T") :: $line" | tee -a $log
   done
 }


# Start process
{

echo 'well'
sleep 3
echo 'hi'
sleep 3
echo 'there'
sleep 3
echo 'sailor'

}  |  teelogger $logfile
3
jrpy

J'étais trop paresseux pour toutes les solutions actuelles ... Alors j'ai trouvé une nouvelle (les œuvres pour stdout peuvent également être ajustées pour stderr):

echo "output" | xargs -L1 -I{} bash -c "echo \$(date +'%x %T') '{}'" | tee error.log

serait enregistrer dans un fichier et imprimer quelque chose comme ça: 11/3/16 16:07:52 output

Détails:

-L1 signifie "pour chaque nouvelle ligne"

-I{} signifie "remplacer {} par une entrée"

bash -c est utilisé pour mettre à jour $(date) à chaque fois pour un nouvel appel

%x %T formate l'horodatage en forme minimale.

Cela devrait fonctionner comme un charme si stdout et stderr ne comporte pas de guillemets ("ou`). S'il a (ou pourrait avoir), il est préférable d'utiliser:

echo "output" | awk '{cmd="(date +'%H:%M:%S')"; cmd | getline d; print d,$0; close(cmd)} | tee error.log'

(Tiré de ma réponse dans un autre sujet: https://stackoverflow.com/a/41138870/868947 )

2
Jimilian

Si vous voulez rediriger vers stdout j'ai trouvé que vous devez faire ceci:

myscript.sh >> >( while read line; do echo "$(date): ${line}"; done )

Je ne sais pas pourquoi j'ai besoin du > devant le (, donc <(, mais c'est ce qui fonctionne.

2
tongueroo

Je pensais ajouter mes 2 centimes.

#!/bin/sh

timestamp(){
  name=$(printf "$1%*s" `expr 15 - ${#1}`)
  awk "{ print strftime(\"%b %d %H:%M:%S\"), \"- $name -\", $$, \"- INFO -\", \$0; fflush() }";
}

echo "hi" | timestamp "process name" >> /tmp/proccess.log

printf "$ 1% * s" `expr 15 - $ {# 1}`
Espace le nom pour qu’il ait l’air agréable, où 15 est l’espace maximal attribué, puis augmentez-le si vous le souhaitez

sorties >> Date - Nom du processus - ID du processus - INFO - Message

Jun 27 13:57:20 - process name     - 18866 - INFO - hi
0
Hovo

Cette chose: Nohup myscript.sh 2>> (pendant la lecture de la ligne; echo "$ (date): $ {line}"; done> mystd.err) </ dev/null &

Fonctionne comme tel, mais lorsque je me déconnecte et que je me reconnecte au serveur, cela ne fonctionne pas. c'est-à-dire que mystd.err cesse de remplir le flux stderr même si mon processus (myscript.sh ici) est toujours exécuté. 

Est-ce que quelqu'un sait comment récupérer le stderr perdu dans le fichier mystd.err?

0
mkc

Les redirections sont prises dans l'ordre. Essaye ça:

Compte tenu d'un script -

$: cat tst
echo a
sleep 2
echo 1 >&2
echo b
sleep 2
echo 2 >&2
echo c
sleep 2
echo 3 >&2
echo d

Je reçois le suivant

$: ./tst 2>&1 1>stdout | sed 's/^/echo $(date +%Y%m%dT%H%M%S) /; e'
20180925T084008 1
20180925T084010 2
20180925T084012 3

Et même si je n'aime pas l'awk, cela évite les sous-appels redondants jusqu'à présent.

$: ./tst 2>&1 1>stdout | awk "{ print strftime(\"%Y%m%dT%H%M%S \") \$0; fflush() }"; >stderr

$: cat stderr
20180925T084414 1
20180925T084416 2
20180925T084418 3

$: cat stdout
a
b
c
d
0
Paul Hodges

Que diriez-vous de l'horodatage de la sortie restante, de tout rediriger vers stdout?

Cette réponse combine certaines techniques d’en haut, ainsi que d’unix stackexchange ici et ici . bash> = 4.2 est supposé, mais d'autres shells avancés peuvent fonctionner. Pour <4.2, remplacez printf par un appel (plus lent) à date.

: ${TIMESTAMP_FORMAT:="%F %T"} # override via environment
_loglines() { 
    while IFS= read -r _line ; do 
      printf "%(${TIMESTAMP_FORMAT})T#%s\n" '-1' "$_line";
    done;
}
exec 7<&2 6<&1
exec &> >( _loglines )
# Logit

Pour restaurer stdout/stderr:

exec 1>&6 2>&7

Vous pouvez ensuite utiliser tee pour envoyer les horodatages à stdout et un fichier journal. 

_tmpfile=$(mktemp)
exec &> >( _loglines | tee $_tmpfile )

Ce n'est pas une mauvaise idée d'avoir un code de nettoyage si le processus s'est terminé sans erreur:

trap "_cleanup \$?" 0 SIGHUP SIGINT SIGABRT SIGBUS SIGQUIT SIGTRAP SIGUSR1 SIGUSR2 SIGTERM
_cleanup() { 
    exec >&6 2>&7
    [[ "$1" != 0 ]] && cat "$_logtemp"
    rm -f "$_logtemp"
    exit "${1:-0}"
}
0
Otheus
#!/bin/bash

DEBUG=1

LOG=$HOME/script_${0##*/}_$(date +%Y.%m.%d-%H.%M.%S-%N).log
ERROR=$HOME/error.log

exec 2> $ERROR
exec 1> >(tee -ai $LOG)

if [ $DEBUG = 0 ] ; then
    exec 4> >(xargs -i echo -e "[ debug ] {}")
else
    exec 4> /dev/null
fi

# test
echo " debug sth " >&4
echo " log sth normal "
type -f this_is_error
echo " errot sth ..." >&2
echo " finish ..." >&2>&4
# close descriptor 4
exec 4>&-
0
borzole