web-dev-qa-db-fra.com

Ajout d'un horodatage à chaque ligne de sortie d'une commande

Je souhaite ajouter un horodatage à chaque ligne de sortie d'une commande. Par exemple:

foo
bar
baz

deviendrait

[2011-12-13 12:20:38] foo
[2011-12-13 12:21:32] bar
[2011-12-13 12:22:20] baz

... où l'heure étant préfixée est l'heure à laquelle la ligne a été imprimée. Comment puis-je atteindre cet objectif?

205
anon

moreutils inclut ts qui le fait très bien:

command | ts '[%Y-%m-%d %H:%M:%S]'

Cela élimine également le besoin d'une boucle, chaque ligne de sortie aura un horodatage.

$ echo -e "foo\nbar\nbaz" | ts '[%Y-%m-%d %H:%M:%S]'
[2011-12-13 22:07:03] foo
[2011-12-13 22:07:03] bar
[2011-12-13 22:07:03] baz

Vous voulez savoir quand ce serveur est revenu, vous avez redémarré? Exécutez simplement ping | ts, problème résolu: D.

311
Mark McKinstry

Premièrement, si vous vous attendez à ce que ces horodatages représentent réellement un événement, gardez à l'esprit que puisque de nombreux programmes effectuent une mise en mémoire tampon de ligne (certains plus agressivement que d'autres), il est important de penser à cela aussi près que le temps que la ligne d'origine aurait été imprimé plutôt que l’horodatage d’une action en cours.

Vous voudrez peut-être également vérifier que votre commande ne dispose pas déjà d'une fonctionnalité intégrée dédiée à cette opération. Par exemple, ping -D existe dans certaines versions de ping et affiche le temps écoulé depuis l'époque Unix avant chaque ligne. Si votre commande ne contient pas sa propre méthode, cependant, il existe quelques méthodes et outils qui peuvent être utilisés, entre autres:

POSIX Shell

Gardez à l'esprit que puisque de nombreux shells stockent leurs chaînes en interne en tant que cstrings, si l'entrée contient le caractère nul (\0), cela peut entraîner la fin prématurée de la ligne.

command | while IFS= read -r line; do printf '[%s] %s\n' "$(date '+%Y-%m-%d %H:%M:%S')" "$line"; done

GNU awk

command | gawk '{ print strftime("[%Y-%m-%d %H:%M:%S]"), $0 }'

Perl

command | Perl -pe 'use POSIX strftime; print strftime "[%Y-%m-%d %H:%M:%S] ", localtime'

Python

command | python -c 'import sys,time;sys.stdout.write("".join(( " ".join((time.strftime("[%Y-%m-%d %H:%M:%S]", time.localtime()), line)) for line in sys.stdin )))'

Ruby

command | Ruby -pe 'print Time.now.strftime("[%Y-%m-%d %H:%M:%S] ")'
112
Chris Down

Pour une mesure delta ligne par ligne, essayez gnomon .

Il s'agit d'un utilitaire de ligne de commande, un peu comme le ts de moreutils, pour ajouter des informations d'horodatage à la sortie standard d'une autre commande. Utile pour les processus de longue durée où vous souhaitez un historique de ce qui prend si longtemps.

Pipeter quoi que ce soit vers gnomon ajoutera un horodatage à chaque ligne, indiquant combien de temps cette ligne a été la dernière ligne du tampon - c'est-à-dire combien de temps il a fallu à la ligne suivante pour apparaître. Par défaut, gnomon affichera les secondes écoulées entre chaque ligne, mais cela est configurable.

gnomon demo

47
Janus Troelsen

La publication de Ryan fournit une idée intéressante, mais elle échoue à plusieurs égards. Lors des tests avec tail -f /var/log/syslog | xargs -L 1 echo $(date +'[%Y-%m-%d %H:%M:%S]') $1, j'ai remarqué que l'horodatage reste le même même si stdout vient plus tard avec une différence en secondes. Considérez cette sortie:

[2016-07-14 01:44:25] Jul 14 01:44:32 eagle dhclient[16091]: DHCPREQUEST of 192.168.0.78 on wlan7 to 255.255.255.255 port 67 (xid=0x411b8c21)
[2016-07-14 01:44:25] Jul 14 01:44:34 eagle avahi-daemon[740]: Joining mDNS multicast group on interface wlan7.IPv6 with address fe80::d253:49ff:fe3d:53fd.
[2016-07-14 01:44:25] Jul 14 01:44:34 eagle avahi-daemon[740]: New relevant interface wlan7.IPv6 for mDNS.

Ma solution proposée est similaire, mais fournit un horodatage approprié et utilise un peu plus portable printf plutôt que echo

| xargs -L 1 bash  -c 'printf "[%s] %s\n" "$(date +%Y-%m-%d\ %H:%M:%S )" "$*" ' bash

Pourquoi bash -c '...' bash? Parce qu'en raison de -c option, le premier argument est affecté à $0 et n'apparaîtra pas dans la sortie. Consultez la page de manuel de votre Shell pour la description appropriée de -c

Test de cette solution avec tail -f /var/log/syslog et (comme vous pouvez probablement le deviner) se déconnecter et se reconnecter à mon wifi, a montré l'horodatage approprié fourni par les messages date et syslog

Bash pourrait être remplacé par n'importe quel shell de type bourne, pourrait être fait avec ksh ou dash, au moins ceux qui ont -c option.

Problèmes potentiels:

La solution nécessite d'avoir xargs, qui est disponible sur les systèmes compatibles POSIX, donc la plupart des systèmes de type Unix doivent être couverts. Évidemment, cela ne fonctionnera pas si votre système n'est pas conforme à POSIX ou n'a pas GNU findutils

7
Sergiy Kolodyazhnyy

J'aurais préféré commenter ci-dessus mais je ne peux pas, de réputation. Quoi qu'il en soit, l'exemple Perl ci-dessus peut être mis en mémoire tampon comme suit:

command | Perl -pe 'use POSIX strftime; 
                    $|=1; 
                    select((select(STDERR), $| = 1)[0]);
                    print strftime "[%Y-%m-%d %H:%M:%S] ", localtime'

Le premier "$ |" débuffeurs STDOUT. Le second définit stderr comme canal de sortie par défaut actuel et le supprime. Étant donné que select renvoie le paramètre d'origine de $ |, en enveloppant le select dans un select, nous réinitialisons également $ | à sa valeur par défaut, STDOUT.

Et oui, vous pouvez couper et coller tel quel. Je l'ai doublé pour plus de lisibilité.

Et si vous voulez vraiment être précis (et que vous avez Time :: Hires installé):

command | Perl -pe 'use POSIX strftime; use Time::HiRes gettimeofday;
                    $|=1; 
                    select((select(STDERR), $| = 1)[0]);
                    ($s,$ms)=gettimeofday();
                    $ms=substr(q(000000) . $ms,-6);
                    print strftime "[%Y-%m-%d %H:%M:%S.$ms]", localtime($s)'
6
mpersico

La plupart des réponses suggèrent d'utiliser date, mais c'est assez lent. Si votre version bash est supérieure à 4.2.0, il vaut mieux utiliser printf à la place, c'est une fonction bash intégrée. Si vous devez prendre en charge les versions bash héritées, vous pouvez créer la fonction log dépend de la version bash:

TIMESTAMP_FORMAT='%Y-%m-%dT%H:%M:%S'
# Bash version in numbers like 4003046, where 4 is major version, 003 is minor, 046 is subminor.
printf -v BV '%d%03d%03d' ${BASH_VERSINFO[0]} ${BASH_VERSINFO[1]} ${BASH_VERSINFO[2]}
if ((BV > 4002000)); then
log() {
    ## Fast (builtin) but sec is min sample for most implementations
    printf "%(${TIMESTAMP_FORMAT})T %5d %s\n" '-1' $$ "$*"  # %b convert escapes, %s print as is
}
else
log() {
    ## Slow (subshell, date) but support nanoseconds and legacy bash versions
    echo "$(date +"${TIMESTAMP_FORMAT}") $$ $*"
}
fi

Voir les différences de vitesse:

user@Host:~$time for i in {1..10000}; do printf "%(${TIMESTAMP_FORMAT})T %s\n" '-1' "Some text" >/dev/null; done

real    0m0.410s
user    0m0.272s
sys     0m0.096s
user@Host:~$time for i in {1..10000}; do echo "$(date +"${TIMESTAMP_FORMAT}") Some text" >/dev/null; done

real    0m27.377s
user    0m1.404s
sys     0m5.432s

UPD: au lieu de $(date +"${TIMESTAMP_FORMAT}") il vaut mieux utiliser $(exec date +"${TIMESTAMP_FORMAT}") ou même $(exec -c date +"${TIMESTAMP_FORMAT}") trop d'accélération de l'exécution.

5
Mikhail

Vous pouvez le faire avec date et xargs:

... | xargs -L 1 echo `date +'[%Y-%m-%d %H:%M:%S]'` $1

Explication:

xargs -L 1 indique à xargs d'exécuter la commande en cours pour chaque 1 ligne d'entrée, et elle passe dans la première ligne en même temps. echo `date +'[%Y-%m-%d %H:%M:%S]'` $1 fait essentiellement écho à la date avec l'argument d'entrée à la fin

1
Ryan