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

182

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 est préfixée est l'heure à laquelle la ligne a été imprimée. Comment puis-je atteindre cet objectif?


la source

Réponses:

274

moreutils comprend tsce qui le fait assez bien:

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

Cela élimine également le besoin d'une boucle, chaque ligne de sortie ayant 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é? Courez ping | ts, problème résolu: D.

Mark McKinstry
la source
8
Comment n'ai-je pas su à ce sujet?!?!?! Cela complète la queue -f étonnamment! tail -f /tmp/script.results.txt | ts
Bruno Bronosky
Qu'en est-il de cygwin? Y a-t-il quelque chose de similaire? Il ne semble pas que les moreutils de Joey soient là.
CrazyPenguin
3
que dois-je utiliser si je n'ai pas la commande ts?
ekassis
1
Si cela ne fonctionne pas, essayez de rediriger stderr vers stdout, par exemplessh -v 127.0.0.1 2>&1 | ts
jchook
3
Je pense que souligner le paramètre -sest utile. Comme cela affiche le runtime de la commande. Personnellement, j'aime utiliser les deux tset ts -sen même temps. On dirait quelque chose comme ceci: command | ts -s '(%H:%M:%.S)]' | ts '[%Y-%m-%d %H:%M:%S'. Ceci ajoute les lignes de journal comme ceci:[2018-12-04 08:31:00 (00:26:28.267126)] Hai <3
BrainStone
100

Tout d'abord, si vous vous attendez à ce que ces horodatages représentent réellement un événement, gardez à l'esprit que, dans la mesure où de nombreux programmes effectuent une mise en mémoire tampon des lignes (certains de manière plus agressive que d'autres), il est important de penser à cela aussi près du temps que la ligne d'origine aurait. été imprimé plutôt que l’horodatage d’une action ayant lieu.

Vous pouvez également vérifier que votre commande ne dispose pas déjà d'une fonctionnalité intégrée dédiée à cela. Par exemple, ping -Dexiste dans certaines pingversions et affiche le temps écoulé depuis l'époque Unix avant chaque ligne. Si votre commande ne contient pas sa propre méthode, vous pouvez utiliser quelques méthodes et outils, entre autres:

Shell POSIX

Gardez à l'esprit que, comme de nombreux shell stockent leurs chaînes en interne en tant que chaînes, si l'entrée contient le caractère nul ( \0), la ligne peut se terminer prématurément.

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

Rubis

command | ruby -pe 'print Time.now.strftime("[%Y-%m-%d %H:%M:%S] ")'
Chris Down
la source
3
Un problème ici est que beaucoup de programmes activent encore plus la mise en tampon de sortie lorsque leur sortie standard est un canal plutôt que le terminal.
cjm
3
@cjm - Vrai. Certains tampons de sortie peuvent être atténués en utilisant stdbuf -o 0, mais si le programme gère manuellement sa mise en tampon de sortie, cela n’aidera pas (sauf s’il existe une option pour désactiver / réduire la taille du tampon de sortie).
Chris Down
2
Pour python, vous pouvez désactiver la mise en mémoire tampon de lignes avecpython -u
ibizaman
@Bwmat No. effectue une ... for x in sys.stdinitération sur les lignes sans les mettre d'abord en mémoire tampon.
Chris Down
Faites ceci et vous obtenez une mise en mémoire tampon ... pour une en 1 1 1 1 1; dormez 1; écho; fait | python -c 'import sys, time; sys.stdout.write ("". join (("" .join ((time.strftime) "" [% Y-% m-% d% H:% M:% S] ", time.gmtime ()), line)) pour line dans sys.stdin))) '
ChuckCottrill
41

Pour une mesure delta ligne par ligne, essayez gnomon .

C'est un utilitaire de ligne de commande, un peu comme ts de moreutils, pour ajouter des informations d'horodatage à la sortie standard d'une autre commande. Utile pour les processus de longue durée pour lesquels vous souhaitez un enregistrement historique de ce qui prend si longtemps.

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

gnomon démo

Janus Troelsen
la source
Cela ressemble à une excellente alternative à l' tsutilisation de processus en direct. While tsest mieux adapté aux processus non interactifs.
BrainStone
7

Le message de Ryan fournit une idée intéressante, mais il échoue à plusieurs égards. En testant 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 s’il stdoutarrive 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.

La solution proposée est similaire, mais fournit une horodatage correcte et utilise un peu plus de portabilité printfque deecho

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

Pourquoi bash -c '...' bash? En raison de l' -coption, le premier argument est attribué à $0et ne sera pas affiché dans la sortie. Consultez la page de manuel de votre shell pour la description correcte de-c

Tester cette solution avec tail -f /var/log/sysloget (comme vous pouvez probablement le deviner) en vous déconnectant et en vous reconnectant à mon réseau wifi, a montré le bon horodatage fourni par les messages dateetsyslog

Bash pourrait être remplacé par n’importe quelle coquille semblable au bourne, pourrait être fait avec l’un kshou l’ autre dash, du moins ceux qui ont une -coption.

Problèmes potentiels:

La solution nécessite la xargsmise à disposition, disponible sur les systèmes compatibles POSIX, de sorte que la plupart des systèmes de type Unix doivent être couverts. De toute évidence, cela ne fonctionnera pas si votre système n’est pas compatible POSIX ou n’a pasGNU findutils

Sergiy Kolodyazhnyy
la source
5

J'aurais préféré commenter ci-dessus mais je ne peux pas, à la réputation. Quoi qu’il en soit, l’échantillon Perl ci-dessus peut être décompressé 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 '$ |' unbuffers STDOUT. Le second définit stderr comme canal de sortie par défaut actuel et le désamorce. Puisque select renvoie le paramètre original de $ |, en repliant la sélection dans une sélection, nous réinitialisons également $ | à sa valeur par défaut, STDOUT.

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

Et si vous voulez vraiment être précis (et que Time :: Hires est 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)'
mpersico
la source
1
Fonctionne comme un charme, sans avoir à installer de packages non standard.
Jay Taylor
2

La plupart des réponses suggèrent d'utiliser date, mais c'est assez lent. Si votre version de bash est supérieure à 4.2.0, il est préférable de l'utiliser à la printfplace, c'est une commande intégrée à bash. Si vous devez prendre en charge des versions anciennes de bash, vous pouvez créer une logfonction qui dépend de la version de 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}")cela, il vaut mieux utiliser $(exec date +"${TIMESTAMP_FORMAT}")ou même $(exec -c date +"${TIMESTAMP_FORMAT}")accélérer l'exécution.

Mikhail
la source
0

Vous pouvez le faire avec dateet xargs:

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

Explication:

xargs -L 1indique à xargs d'exécuter la commande précédente pour chaque ligne d'entrée et passe dans la première ligne comme il le fait. echo `date +'[%Y-%m-%d %H:%M:%S]'` $1fait essentiellement écho à la date avec l'argument d'entrée à la fin de celui-ci

Ryan
la source
2
La solution est proche, mais l'horodatage n'est pas correct lorsqu'il s'agit d'une sortie séparée par de longues périodes. En outre, vous utilisez des backticks et n'avez pas encore cité $1. Ce n'est pas bon style. Citez toujours les variables. De plus, vous utilisez echo, ce qui n’est pas portable. Tout va bien, mais peut ne pas fonctionner correctement sur certains systèmes.
Sergiy Kolodyazhnyy
Après avoir testé cela, il semble que vous ayez absolument raison ... connaissez-vous un moyen de faire dateréévaluer chaque ligne, ou est-ce à peu près sans espoir?
Ryan