Jeg ønsker å legge en tidsstempel til hver utgangslinje fra en kommando. For eksempel:
foo bar baz
ville blitt
[2011-12-13 12:20:38] foo [2011-12-13 12:21:32] bar [2011-12-13 12:22:20] baz
… hvor foreløpig prefikset er tidspunktet da linjen ble skrevet ut. Hvordan kan jeg oppnå dette?
Kommentarer
- Finnes det et Unix-verktøy for å forutsette tidsstempler til stdin?
Svar
moreutils inkluderer ts
som gjør dette ganske pent:
command | ts "[%Y-%m-%d %H:%M:%S]"
It eliminerer behovet for en sløyfe også, hver utgangslinje vil ha et tidsstempel.
$ 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
Vil du vite når den serveren kom opp igjen og startet på nytt? Bare kjør ping | ts
, problemet er løst: D.
Kommentarer
- Hvordan har jeg ikke visst om dette ?!?!?! Dette utfyller halen -f utrolig!
tail -f /tmp/script.results.txt | ts
- Hvis jeg ikke har ‘ t har ts-kommando, hva skal jeg bruke?
- Hvis det ‘ ikke fungerer, kan du prøve å omdirigere stderr til stdout f.eks.
ssh -v 127.0.0.1 2>&1 | ts
- Installer ved å gjøre
sudo apt install moreutils
på Debian ogyum install moreutils
på Fedora. - Jeg synes det er nyttig å påpeke parameteren
-s
. Som det viser kjøretiden til kommandoen. Jeg liker personlig å bruke bådets
ogts -s
samtidig. Ser omtrent slik ut:command | ts -s '(%H:%M:%.S)]' | ts '[%Y-%m-%d %H:%M:%S'
. Dette forbereder logglinjene slik:[2018-12-04 08:31:00 (00:26:28.267126)] Hai <3
Svar
For det første, hvis du forventer at disse tidsstemplene faktisk skal representere en begivenhet, må du huske at siden mange programmer utfører linjebuffering (noen mer aggressivt enn andre), er det viktig å tenke på dette så nær den tid som den opprinnelige linjen har blitt skrevet ut i stedet for en tidsstempel for en handling som finner sted.
Det kan også være lurt å sjekke at kommandoen ikke allerede har en innebygd funksjon dedikert til å gjøre dette. Som et eksempel, ping -D
finnes i noen ping
-versjoner, og skriver ut tiden siden Unix-epoken før hver linje. Hvis kommandoen din ikke inneholder sin egen metode, der er noen få metoder og verktøy som kan brukes, blant annet:
POSIX skall
Husk at siden mange skjell lagrer strengene sine internt som strenger, hvis inngangen inneholder null røye acter (\0
), kan det føre til at linjen slutter for tidlig.
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] ")"
Kommentarer
- Et problem her er at mange programmer slår på enda mer utgangsbuffering når stdout er et rør i stedet for terminalen.
- @cjm – Sant. Noe utgangsbuffering kan lindres ved å bruke
stdbuf -o 0
, men hvis programmet håndterer utgangsbufferen manuelt, vant den ‘ t hjelp (med mindre det er et alternativ å deaktivere / redusere størrelsen på utgangsbufferen. - For python kan du deaktivere linjebuffering med
python -u
- @Bwmat nr.
... for x in sys.stdin
itererer over linjer uten å buffere dem alle i minnet først. - Gjør dette så får du buffering … for en i 1 1 1 1 1; sove 1; ekko; gjort | python -c ‘ importer sys, time; sys.stdout.write (» » . gå sammen ((» » .join ((time.strftime (» [%) Y-% m-% d% H:% M:% S] «, time.gmtime ()), linje)) for linje i sys.stdin))) ‘
Svar
For en delta-måling linje for linje , prøv gnomon .
Det er et kommandolinjeverktøy, litt som mer verktøy «s ts, for å forutse tidsstempelinformasjon til standardutdata fra en annen kommando. Nyttig for langvarige prosesser der du vil ha en historisk oversikt over hva som tar så lang tid.
Piping noe til gnomon vil forutsette et tidsstempel til hver linje, som indikerer hvor lang linjen var den siste linjen i bufferen – det vil si hvor lang tid det tok neste linje å vises. Som standard vil gnomon vise de forløpne sekundene mellom hver linje, men det kan konfigureres.
Kommentarer
- Ser ut som et flott alternativ til
ts
når du bruker live-prosesser. Mensts
er bedre egnet for ikke-interaktive prosesser.
Svar
Jeg hadde foretrukket å kommentere ovenfor, men jeg kan ikke, omdømme. Uansett, Perl-prøven ovenfor kan puffes ut som følger:
command | perl -pe "use POSIX strftime; $|=1; select((select(STDERR), $| = 1)[0]); print strftime "[%Y-%m-%d %H:%M:%S] ", localtime"
The første «$ |» puffer ut STDOUT. Den andre setter stderr som den gjeldende standardutgangskanalen og puffer den opp. Siden select returnerer den opprinnelige innstillingen på $ | ved å pakke select i en select, tilbakestiller vi også $ | STDOUT.
Og ja, du kan klippe «n lim inn som den er. Jeg flerlinjer det for lesbarhet.
Og hvis du virkelig vil bli presis (og du har Time :: Hires installert):
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)"
Kommentarer
- Fungerer som en sjarm, uten å måtte installere noen ikke-standardpakker.
Svar
Ryans innlegg gir en interessant idé, men den mislykkes i flere henseender. Mens jeg testet med tail -f /var/log/syslog | xargs -L 1 echo $(date +"[%Y-%m-%d %H:%M:%S]") $1
, la jeg merke til at tidsstemplet forblir det samme selv om stdout
kommer senere med forskjell i sekunder fra hverandre. Tenk på denne utgangen:
[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.
Min foreslåtte løsning er lik, men gir riktig tidsstempling og bruker noe mer bærbar printf
i stedet for echo
| xargs -L 1 bash -c "printf "[%s] %s\n" "$(date +%Y-%m-%d\ %H:%M:%S )" "$*" " bash
Hvorfor bash -c "..." bash
? På grunn av -c
-alternativet, argumenter først nt blir tildelt $0
og vil ikke dukke opp i utdataene. Se shell-manualens side for riktig beskrivelse av -c
Test av denne løsningen med tail -f /var/log/syslog
og (som du sannsynligvis kunne gjette deg) å koble fra og koble til wifi på nytt, har vist riktig tidsstempling levert av både date
og syslog
meldinger
Bash kan erstattes av et hvilket som helst bourne-lignende skall, kan gjøres med enten ksh
eller dash
, i det minste de som har -c
alternativ.
Potensielle problemer:
Løsningen krever å ha xargs
, som er tilgjengelig på POSIX-kompatible systemer, så de fleste Unix-lignende systemer bør dekkes. Åpenbart vil ikke fungere hvis systemet ditt ikke er POSIX-kompatibelt eller ikke har GNU findutils
Svar
De fleste svarene foreslår å bruke date
, men det er sakte nok . Hvis din bash-versjon er større enn 4.2.0, er det bedre å bruke printf
i stedet, det er en bash-innebygd. Hvis du trenger å støtte eldre bash-versjoner, kan du opprette log
-funksjonen avhenger av bash-versjonen:
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
Se hastighet forskjeller:
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: i stedet for $(date +"${TIMESTAMP_FORMAT}")
er det bedre å bruke $(exec date +"${TIMESTAMP_FORMAT}")
eller til og med $(exec -c date +"${TIMESTAMP_FORMAT}")
for raskere kjøring.
UPD2: bash 5 gir EPOCHREALTIME
-variabel for mikrosekunder granularitet, du kan bruke den med denne kommandoen (ca. 30% langsommere enn bare sekunder): printf "%(${TIMESTAMP_FORMAT})T.%s %5d %s\n" ${EPOCHREALTIME/./ } $$ "$*"
Kommentarer
- Dette svaret er veldig undervurdert! Spesielt hyggelig å vise den relative ytelsespåvirkningen.
- Utmerket forslag, men dessverre gir ikke dato oppløsning på andre sekund, noe som er et krav i mitt tilfelle
- Med bash 5.0+ kan du bruke denne kommandoen:
printf "%(${TIMESTAMP_FORMAT})T.%s %5d %s\n" ${EPOCHREALTIME/./ } $$ "$*"
Svar
Du kan gjøre dette med date
og xargs
:
... | xargs -L 1 echo `date +"[%Y-%m-%d %H:%M:%S]"` $1
Forklaring:
xargs -L 1
forteller xargs å kjøre kommandoen for hver 1 inngangslinje, og den passerer i første linje når den gjør det. echo `date +"[%Y-%m-%d %H:%M:%S]"` $1
utgir i utgangspunktet datoen med inngangsargumentet på slutten av den
Kommentarer
- Løsningen er nær, men
tidsstempler ikke riktig når det gjelder utdata atskilt med lange tidsperioder. Du ‘ bruker også backticks og har ikke ‘ t sitert$1
. At ‘ ikke er god stil. Sitat alltid på variablene. I tillegg bruker du ‘
echo
, som ikke er bærbar. Det ‘ er greit, men fungerer kanskje ikke riktig på noen systemer.
date
bli revurdert hver linje, eller er det ganske håpløst? Svar
Skamløs plugg for noe jeg skrev nettopp for å løse akkurat dette problemet: ets
, skrevet i Go.
Du kan finne mange brukseksempler på prosjektsiden.
Den bemerkelsesverdige forskjellen fra eksisterende svar og lignende tilbud er at ets
er designet for å kjøre kommandoen din for deg, i en pty (pseudo tty) – det vil si å simulere kommandoen din kjører innfødt i tty. Sammenlignet med piping-kommandoutgang til f.eks. ts
, dette gjør tidsstempling for det meste gjennomsiktig, og løser en rekke problemer med piping:
- Noen programmer buffer aggressivt når du skriver til et rør, så du ser ingen utdata og deretter en hel haug med utdata (ja, du kan stdbuf dem, du kan til og med pakke stdbuf og ts i et alias / funksjon, men det ville ikke vært bedre hvis ting fungerer ut av esken);
- Noen programmer deaktiverer farge og / eller interaktivitet når du skriver til et rør.
- Utgangsstatus er borte med mindre du har slått på pipefail osv.
Kommandoer kan bli direkte utført, noe som betyr at du bare kan legge ets
til din eksisterende kommandolinje, eller de kan være skallkommandoer (som vist i gifen ovenfor). Selvfølgelig, hvis du vil rense utdata, kan ets
også gjøre det.
ets
støtter det samme tidsstempelmodus som merutstyr ts
: absolutt tidsmodus, forløpt tidsmodus og inkrementell tidsmodus. Den bruker fornuftige standardverdier (f.eks. Brukes monoton klokke alltid til forløpte / inkrementelle tidsstempler) og har ekstra støtte for tilpassede tidssoner. Det er en detaljert sammenligning her .
Igjen, https://github.com/zmwangx/ets . Gi den et snurr, rapporter feil, etc.
Svar
Sett dette nær starten av skriptet ditt
# prepend a timestamp to all output exec &> >( ts "%Y-%m-%d.%H%M.%.S " )
Eller, for ekstra kreditt, send til loggfilen via:
script_name=foobar log_file=$( printf "/tmp/${script_name}-%(%Y-%m-%d)T.%(%H%M%S)T.log" -1 ) echo "note: redirecting output to [${log_file}]" exec &> >( ts "%Y-%m-%d %H:%M:%.S " > ${log_file} )
for å sende til både konsoll- og loggfil:
script_name=foobar log_file=$( printf "/tmp/${script_name}-%(%Y-%m-%d)T.%(%H%M%S)T.log" -1 ) exec &> >( ts "%Y-%m-%d %H:%M:%.S " | tee ${log_file} )
De viktigste fordelene ved å gjøre dette er å skille loggføringen fra alt annet, ikke rotet skriptets kropp ved å røre til tee
eller lignende på hver kommando, og ikke å måtte skrive egendefinerte loggfunksjoner og holde deg til vanlig gammel echo
.
ts
-programmet finnes i moreutils
-pakken, som skal være lett tilgjengelig under enhver rimelig tilregnelig administrasjonsregime. 🙂