Förbereda en tidsstämpel till varje utdatarad från ett kommando

Jag vill förbereda en tidsstämpel till varje utdatarad från ett kommando. Till exempel:

foo bar baz 

skulle bli

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

… där för tillfället prefix är den tidpunkt då raden skrevs ut. Hur kan jag uppnå detta?

Kommentarer

Svar

fler användningsområden inkluderar ts vilket gör detta ganska snyggt:

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

It eliminerar också behovet av en slinga, varje utgångsrad kommer att ha en tidsstämpel.

$ 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 

Vill du veta när den servern kom tillbaka och du startade om? Kör bara ping | ts, problem löst: D.

Kommentarer

  • Hur har jag inte känt till detta ?!?!?! Detta kompletterar svansen -f otroligt! tail -f /tmp/script.results.txt | ts
  • om jag inte ’ inte har ts-kommando, vad ska jag använda?
  • Om det ’ inte fungerar, försök att omdirigera stderr till stdout t.ex. ssh -v 127.0.0.1 2>&1 | ts
  • Installera genom att göra sudo apt install moreutils på Debian och yum install moreutils på Fedora.
  • Jag tycker att det är användbart att peka på parametern -s. Eftersom det visar körtiden för kommandot. Jag gillar personligen att använda både ts och ts -s samtidigt. Ser ungefär så här ut: command | ts -s '(%H:%M:%.S)]' | ts '[%Y-%m-%d %H:%M:%S'. Detta förbereder logglinjerna så här: [2018-12-04 08:31:00 (00:26:28.267126)] Hai <3

Svar

För det första, om du förväntar dig att dessa tidsstämplar faktiskt representerar en händelse, kom ihåg att eftersom många program utför linjebuffring (vissa mer aggressivt än andra), är det viktigt att tänka på detta nära den tid som den ursprungliga har skrivits ut snarare än en tidsstämpel för en åtgärd som äger rum.

Du kanske också vill kontrollera att ditt kommando inte redan har en inbyggd funktion som är avsedd för att göra detta. Som ett exempel, ping -D finns i vissa ping versioner och skriver ut tiden sedan Unix-epoken före varje rad. Om ditt kommando inte innehåller sin egen metod finns det däremot är några metoder och verktyg som kan användas, bland annat:

POSIX-skal

Tänk på att eftersom många skal lagrar sina strängar internt som strängar, om ingången innehåller noll röding acter (\0) kan det leda till att raden slutar för tidigt.

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

  • Ett problem här är att många program slås på ännu mer utmatningsbuffring när deras stdout är ett rör istället för terminalen.
  • @cjm – True. Vissa utmatningsbuffertar kan lindras med stdbuf -o 0, men om programmet hanterar sin utmatningsbuffring manuellt, får det ’ t hjälp (såvida inte det finns ett alternativ att inaktivera / minska storleken på utmatningsbufferten.
  • För python kan du inaktivera radbuffring med python -u
  • @Bwmat nr. ... for x in sys.stdin itererar över rader utan att buffra dem alla i minnet först.
  • Gör detta så får du buffring … för en i 1 1 1 1 1; sov 1; eko; gjort | python -c ’ importera sys, tid; sys.stdout.write (” ” . gå med ((” ” .join ((time.strftime (” [%) Y-% m-% d% H:% M:% S] ”, time.gmtime ()), rad)) för rad i sys.stdin))) ’

Svar

För en delta-mätning rad för rad , prova gnomon .

Det är ett kommandoradsverktyg, lite som meranvändningar ”s ts, för att förbereda tidsstämpelinformation till standardutmatningen för ett annat kommando. Användbart för långvariga processer där du vill ha en historisk post över vad som tar så lång tid.

Pipa allt till gnomon kommer förbereda en tidsstämpel för varje rad, som anger hur länge den raden var den sista raden i bufferten – det vill säga hur lång tid det tog nästa rad att visas. Som standard visar gnomon de sekunder som har gått mellan varje rad, men det är konfigurerbart.

gnomon demo

Kommentarer

  • Ser ut som ett bra alternativ till ts när du använder live-processer. Medan ts passar bättre för icke-interaktiva processer.

Svar

Jag skulle ha föredragit att kommentera ovan men jag kan inte, ryktefullt. Hur som helst, Perl-exemplet ovan kan opuffas på följande sätt:

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

första ”$ |” frigör STDOUT. Den andra sätter stderr som den aktuella standardutgångskanalen och frigör den. Eftersom select returnerar den ursprungliga inställningen $ | genom att slå in select i en select, återställer vi också $ | till standard, STDOUT.

Och ja, du kan klippa in och klistra in som det är. Jag flernadade den för läsbarhet.

Och om du verkligen vill bli exakt (och du har Time :: Hires installerat):

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

  • Fungerar som en charm utan att behöva installera några icke-standardpaket.

Svar

Ryans inlägg ger en intressant idé, men den misslyckas i flera avseenden. När jag testade med tail -f /var/log/syslog | xargs -L 1 echo $(date +"[%Y-%m-%d %H:%M:%S]") $1, såg jag att tidsstämpeln förblir densamma även om stdout kommer senare med skillnad i sekunder från varandra. Tänk på den här utgången:

[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 föreslagna lösning är likartad men ger rätt tidsstämpling och använder något mer bärbar printf snarare än echo

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

Varför bash -c "..." bash? På grund av -c -alternativet, argumentera först nt tilldelas $0 och kommer inte att visas i utdata. Se din skalhandbok för korrekt beskrivning av -c

Testa denna lösning med tail -f /var/log/syslog och (som du kan antagligen gissa) koppla bort och ansluta till min wifi, har visat rätt tidsstämpling från både date och syslog meddelanden

Bash kan ersättas med vilket bornsliknande skal som helst, kan göras med antingen ksh eller dash, åtminstone de som har -c -alternativ.

Potentiella problem:

Lösningen kräver att xargs, som är tillgängligt på POSIX-kompatibla system, så de flesta Unix-liknande system bör täckas. Självklart kommer det inte att fungera om ditt system inte är POSIX-kompatibelt eller inte har GNU findutils

Svar

De flesta av svaren föreslår att du använder date, men det är tillräckligt långsamt . Om din bash-version är större än 4.2.0 är det bättre att använda printf istället är det ett inbyggt bash. Om du behöver stödja äldre bash-versioner kan du skapa log -funktionen beror på 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 hastighet skillnader:

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: istället för $(date +"${TIMESTAMP_FORMAT}") är det bättre att använda $(exec date +"${TIMESTAMP_FORMAT}") eller till och med $(exec -c date +"${TIMESTAMP_FORMAT}") för snabb körning.

UPD2: bash 5 ger EPOCHREALTIME variabel för mikrosekunder granularitet, du kan använda det med det här kommandot (cirka 30% långsammare än bara sekunder): printf "%(${TIMESTAMP_FORMAT})T.%s %5d %s\n" ${EPOCHREALTIME/./ } $$ "$*"

Kommentarer

  • Detta svar är väldigt underskattat! Särskilt trevligt att visa den relativa prestandapåverkan.
  • Utmärkt förslag, men tyvärr ger datum inte upplösning på andra sekund, vilket är ett krav i mitt fall
  • Med bash 5.0+ kan du använda det här kommandot: printf "%(${TIMESTAMP_FORMAT})T.%s %5d %s\n" ${EPOCHREALTIME/./ } $$ "$*"

Svar

Du kan göra detta med date och xargs:

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

Förklaring:

xargs -L 1 säger till xargs att köra kommandot för varje ingångsrad, och det passerar i första raden när det gör det. echo `date +"[%Y-%m-%d %H:%M:%S]"` $1 upprepar i grund och botten datumet med inmatningsargumentet i slutet av det

Kommentarer

  • Lösningen är nära, men ’ stämmer inte korrekt när det gäller utdata åtskilda av långa tidsperioder. Du ’ använder också backticks och har inte ’ t citerat $1. Att ’ inte är bra stil. Citera alltid variablerna. Dessutom använder du ’ echo, som inte är bärbar. Det ’ är okej, men kanske inte fungerar korrekt på vissa system.
  • Efter att ha testat detta verkar det som om du ’ har helt rätt … känner du till något sätt att göra date bli omvärderad varje rad, eller är det ganska hopplöst?

Svar

Skamlös kontakt för något jag skrev precis för att lösa detta exakta problem: ets , skrivet i Go.

demo

Du kan hitta många användningsexempel på projektsidan.

Den anmärkningsvärda skillnaden från befintliga svar och liknande erbjudanden är att ets är utformad för att köra ditt kommando åt dig, i en pty (pseudo tty) – det vill säga simulera ditt kommando körs inbyggt i tty. Jämfört med piping-kommandoutgång till t.ex. ts, detta gör tidsstämpling mestadels transparent och löser en massa problem med piping:

  • Vissa program buffras aggressivt när du skriver till ett pip, så du ser ingen output och sedan en hel massa output (ja, du kan stdbuf dem, du kan till och med slå in stdbuf och ts i ett alias / funktion, men skulle inte vara bättre om saker fungerar ur lådan);
  • Vissa program inaktiverar färg och / eller interaktivitet när du skriver till ett rör.
  • Avslutningsstatus är borta om du inte har aktiverat pipefail; etc.

Kommandon kan utförs direkt, vilket innebär att du helt enkelt kan förlita ets till din befintliga kommandorad, eller så kan de vara skalkommandon (som visas i gifen ovan). Självklart kan ets göra det också.

ets stöder samma tidsstämplägen som merutnyttjande ts: absolut tidsläge, förfluten tidsläge och inkrementellt tidsläge. Den använder förnuftigare standardinställningar (t.ex. monoton klocka används alltid för förflutna / inkrementella tidsstämplar) och har ytterligare stöd för anpassade tidszoner. Det finns en detaljerad jämförelse här .

Återigen https://github.com/zmwangx/ets . Ge det en snurr, rapportera buggar osv.

Svar

Sätt detta nära början av ditt skript

# prepend a timestamp to all output exec &> >( ts "%Y-%m-%d.%H%M.%.S " ) 

Eller, för extra kredit, skriv ut till 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} ) 

för att mata ut till både konsol- och 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 största fördelarna med att göra detta är att separera loggningen från allt annat, inte röra skriptets kropp genom att pipa till tee eller liknande på varje kommando och inte behöva skriva anpassade loggningsfunktioner och hålla sig till vanligt echo.

ts -programmet finns i moreutils -paketet, som bör vara lättillgängligt under alla rimliga sane administration regime. 🙂

Lämna ett svar

Din e-postadress kommer inte publiceras. Obligatoriska fält är märkta *