Jeg ønsker at lægge et tidsstempel på hver outputlinje fra en kommando. For eksempel:
foo bar baz
bliver
[2011-12-13 12:20:38] foo [2011-12-13 12:21:32] bar [2011-12-13 12:22:20] baz
… hvor tiden er præfikset er det tidspunkt, hvor linjen blev udskrevet. Hvordan kan jeg opnå dette?
Kommentarer
- Er der et Unix-værktøj til at forudbestille tidsstempler til stdin?
Svar
flere anvendelser inkluderer ts
hvilket gør dette ganske pænt:
command | ts "[%Y-%m-%d %H:%M:%S]"
It eliminerer også behovet for en sløjfe, hver outputlinje har 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 vide, hvornår serveren kom tilbage, du genstartede? Kør bare ping | ts
, problemet er løst: D.
Kommentarer
- Hvordan har jeg ikke vidst om dette ?!?!?! Dette supplerer utroligt halen – f!
tail -f /tmp/script.results.txt | ts
- hvis jeg ikke ‘ ikke har ts-kommando, hvad skal jeg bruge?
- Hvis det ‘ ikke fungerer, kan du prøve at omdirigere stderr til stdout f.eks.
ssh -v 127.0.0.1 2>&1 | ts
- Installér ved at udføre
sudo apt install moreutils
på Debian ogyum install moreutils
på Fedora. - Jeg synes det er nyttigt at påpege parameteren
-s
. Da det viser kørselstiden for kommandoen. Jeg kan godt lide at bruge bådets
ogts -s
på samme tid. Ser sådan ud:command | ts -s '(%H:%M:%.S)]' | ts '[%Y-%m-%d %H:%M:%S'
. Dette foregår loglinjerne således:[2018-12-04 08:31:00 (00:26:28.267126)] Hai <3
Svar
For det første, hvis du forventer, at disse tidsstempler faktisk repræsenterer en begivenhed, skal du huske på, at da mange programmer udfører linjebuffering (nogle mere aggressivt end andre), er det vigtigt at tænke på dette tæt på det tidspunkt, hvor den oprindelige linje er udskrevet i stedet for et tidsstempel for en handling, der finder sted.
Det kan også være en god idé at kontrollere, at din kommando ikke allerede har en indbygget funktion dedikeret til at gøre dette. Som et eksempel, ping -D
findes i nogle ping
versioner og udskriver tiden siden Unix-epoken før hver linje. Hvis din kommando ikke indeholder sin egen metode, er der er et par metoder og værktøjer, der kan bruges, blandt andre:
POSIX shell
Husk, at da mange skaller gemmer deres strenge internt som cstrings, hvis input indeholder null char acter (\0
), kan det medføre, at linjen slutter for tidligt.
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 tænder endnu mere outputbuffering, når deres stdout er et rør i stedet for terminalen.
- @cjm – Sandt. Nogle outputbuffere kan afhjælpes ved hjælp af
stdbuf -o 0
, men hvis programmet håndterer sin outputbuffer manuelt, vinder det ‘ t hjælp (medmindre der er en mulighed for at deaktivere / reducere størrelsen på outputbufferen). - For python kan du deaktivere linjebuffering med
python -u
- @Bwmat nr.
... for x in sys.stdin
itererer over linjer uden at buffere dem alle i hukommelsen først. - Gør dette, og du får buffering … for en i 1 1 1 1 1; sove 1; ekko; færdig | python -c ‘ importerer sys, tid; sys.stdout.write (” ” .forbinde ((” ” .join ((time.strftime (” [%) Y-% m-% d% H:% M:% S] “, time.gmtime ()), linje)) for linje i sys.stdin))) ‘
Svar
For en line-for-line delta måling , prøv gnomon .
Det er et kommandolinjeprogram, lidt som flere anvendelser “s ts, for at forberede tidsstempeloplysninger til standardoutputtet for en anden kommando. Nyttigt til langvarige processer, hvor du gerne vil have en historisk oversigt over, hvad der tager så lang tid.
Piping alt til gnomon vil Forhæng et tidsstempel til hver linje, der angiver, hvor lang tid den linje var den sidste linje i bufferen – det vil sige, hvor lang tid det tog den næste linje at blive vist. Som standard viser gnomon de forløbne sekunder mellem hver linje, men det kan konfigureres.
Kommentarer
- Ser ud som et godt alternativ til
ts
, når du bruger live processer. Mensts
er bedre egnet til ikke-interaktive processer.
Svar
Jeg ville have foretrukket at kommentere ovenfor, men jeg kan ikke, velrenommeret. Under alle omstændigheder kan Perl-prøven ovenfor opbufres som følger:
command | perl -pe "use POSIX strftime; $|=1; select((select(STDERR), $| = 1)[0]); print strftime "[%Y-%m-%d %H:%M:%S] ", localtime"
første “$ |” udbuffer STDOUT. Den anden indstiller stderr som den aktuelle standardudgangskanal og opbuffer den. Da select returnerer den oprindelige indstilling på $ | ved at indpakke select i en select, nulstiller vi også $ | til dens standard, STDOUT.
Og ja, du kan klippe “n pasta som den er. Jeg har foret flere linjer for læsbarhed.
Og hvis du virkelig vil være præcis (og du har Time :: Hires installeret):
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
- Virker som en charme uden at skulle installere ikke-standardpakker.
Svar
Ryans indlæg giver dog en interessant idé, men det fejler i flere henseender. Mens jeg testede med tail -f /var/log/syslog | xargs -L 1 echo $(date +"[%Y-%m-%d %H:%M:%S]") $1
, bemærkede jeg, at tidsstemplet forbliver det samme, selvom stdout
kommer senere med forskel i sekunder fra hinanden. Overvej dette output:
[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åede løsning er ens, men giver korrekt tidsstempling og bruger noget mere 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
? Fordi på grund af -c
mulighed, argumenter først nt bliver tildelt $0
og vil ikke dukke op i output. Se manualen til din shell for den korrekte beskrivelse af -c
Test af denne løsning med tail -f /var/log/syslog
og (som du sandsynligvis kunne gætte) at afbryde og genoprette forbindelse til min wifi har vist den korrekte tidsstempling leveret af både date
og syslog
meddelelser
Bash kunne erstattes af enhver bourne-lignende skal, kunne udføres med enten ksh
eller dash
, i det mindste de der har -c
mulighed.
Potentielle problemer:
Løsningen kræver at have xargs
, som er tilgængelig på POSIX-kompatible systemer, så de fleste Unix-lignende systemer skal dækkes. Det fungerer åbenbart ikke, hvis dit system ikke er POSIX-kompatibelt eller ikke har GNU findutils
Svar
De fleste af svarene foreslår at bruge date
, men det er langsomt nok . Hvis din bash-version er større end 4.2.0, er det bedre at bruge printf
i stedet for, det er en bash-indbygget. Hvis du har brug for at understøtte ældre bash-versioner, kan du oprette log
-funktionen afhænger af bash-version:
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 hastighed forskelle:
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 at bruge $(exec date +"${TIMESTAMP_FORMAT}")
eller endda $(exec -c date +"${TIMESTAMP_FORMAT}")
for hurtigere udførelse.
UPD2: bash 5 giver EPOCHREALTIME
-variabel til mikrosekunder granularitet, du kan bruge den ved denne kommando (kun ca. 30% langsommere end sekunder): printf "%(${TIMESTAMP_FORMAT})T.%s %5d %s\n" ${EPOCHREALTIME/./ } $$ "$*"
Kommentarer
- Dette svar er meget undervurderet! Særligt rart at vise den relative ydeevneeffekt.
- Fremragende forslag, men desværre giver datoen ikke anden sekunders opløsning, hvilket er et krav i mit tilfælde
- Med bash 5.0+ kan du bruge denne kommando:
printf "%(${TIMESTAMP_FORMAT})T.%s %5d %s\n" ${EPOCHREALTIME/./ } $$ "$*"
Svar
Du kan gøre dette med date
og xargs
:
... | xargs -L 1 echo `date +"[%Y-%m-%d %H:%M:%S]"` $1
Forklaring:
xargs -L 1
fortæller xargs at køre den fortsatte kommando for hver 1 inputlinje, og den passerer i første linje, når den gør det. echo `date +"[%Y-%m-%d %H:%M:%S]"` $1
dybest set datoen med inputargumentet i slutningen af det
Kommentarer
- Løsningen er tæt, men
tidsstempler ikke korrekt, når det kommer til output adskilt af lange perioder. Du ‘ bruger også backticks og har ikke ‘ t citeret$1
. At ‘ ikke er god stil. Citér altid variablerne. Derudover bruger du ‘
echo
, som ikke er bærbar. Det ‘ er okay, men fungerer muligvis ikke korrekt på nogle systemer.
date
blive revurderet hver linje, eller er det temmelig håbløst? Svar
Skamløst stik til noget, jeg skrev lige for at løse dette nøjagtige problem: ets
, skrevet i Go.
Du kan finde mange brugseksempler på projektsiden.
Den bemærkelsesværdige forskel fra eksisterende svar og lignende tilbud er, at ets
er designet til at køre din kommando for dig i en pty (pseudo tty) – det vil sige simulere din kommando kører indbygget i en tty. Sammenlignet med piping-kommandoutput til f.eks. ts
, dette gør tidsstempling for det meste gennemsigtig og løser en masse problemer med piping:
- Nogle programmer buffer aggressivt, når du skriver til et rør, så du se ingen output og derefter en hel masse output (ja, du kan stdbuf dem, du kan endda pakke stdbuf og ts i et alias / funktion, men ville det ikke være bedre, hvis tingene fungerer ud af kassen);
- Nogle programmer deaktiverer farve og / eller interaktivitet, når du skriver til et rør;
- Status for udgang er væk, medmindre du har slået pipefail til osv.
Kommandoer kan udføres direkte, hvilket betyder at du simpelthen kan forlænge ets
til din eksisterende kommandolinje, eller de kan være shell-kommandoer (som vist i gifen ovenfor). Selvfølgelig, hvis du vil pibe output i, kan ets
også gøre det.
ets
understøtter det samme tidsstempeltilstande som flere anvendelser ts
: absolut tidstilstand, forløbet tidstilstand og trinvis tidstilstand. Det bruger mere standardindstillinger (f.eks. Bruges monotont ur altid til forløbne / inkrementelle tidsstempler) og har yderligere support til brugerdefinerede tidszoner. Der er en detaljeret sammenligning her .
Igen https://github.com/zmwangx/ets . Giv det et spin, rapporter bugs osv.
Svar
Sæt dette nær starten af dit script
# prepend a timestamp to all output exec &> >( ts "%Y-%m-%d.%H%M.%.S " )
Eller for ekstra kredit, output til logfil 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} )
til output til både konsol- og logfil:
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 største fordele ved at gøre dette er at adskille logning fra alt andet, ikke rod i selve scriptet ved at føre til tee
eller lignende på hver kommando og ikke behøver at skrive brugerdefinerede logfunktioner og holde fast i almindelig echo
.
ts
-programmet findes i moreutils
-pakken, som skal være let tilgængelig under enhver rimelig sundt styre regime. 🙂