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?
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.
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:
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
command | gawk '{ print strftime("[%Y-%m-%d %H:%M:%S]"), $0 }'
command | Perl -pe 'use POSIX strftime; print strftime "[%Y-%m-%d %H:%M:%S] ", localtime'
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 )))'
command | Ruby -pe 'print Time.now.strftime("[%Y-%m-%d %H:%M:%S] ")'
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.
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.
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
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)'
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.
Vous pouvez le faire avec date
et xargs
:
... | xargs -L 1 echo `date +'[%Y-%m-%d %H:%M:%S]'` $1
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