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
- Finns det ett Unix-verktyg för att förbereda tidsstämplar till stdin?
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 ochyum 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ådets
ochts -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.
Kommentarer
- Ser ut som ett bra alternativ till
ts
när du använder live-processer. Medants
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.
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. 🙂