Voorbereiden van een tijdstempel voor elke uitvoerregel met een commando

Ik wil een tijdstempel toevoegen aan elke uitvoerregel van een commando. Bijvoorbeeld:

foo bar baz 

zou worden

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

… waar de tijd is voorafgegaan is het tijdstip waarop de regel werd afgedrukt. Hoe kan ik dit bereiken?

Opmerkingen

Antwoord

moreutils bevat ts wat dit heel goed doet:

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

Het elimineert ook de noodzaak van een lus, elke regel met uitvoer zal een tijdstempel hebben.

$ 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 

Wil je weten wanneer die server terugkwam en herstartte? Voer gewoon ping | ts uit, probleem opgelost: D.

Opmerkingen

  • Hoe weet ik dit niet ?!?!?! Dit vult staart -f verbazingwekkend aan! tail -f /tmp/script.results.txt | ts
  • als ik ‘ geen ts commando heb, wat moet ik dan gebruiken?
  • Als het ‘ niet werkt, probeer dan stderr om te leiden naar stdout, bijv. ssh -v 127.0.0.1 2>&1 | ts
  • Installeer door sudo apt install moreutils op Debian en yum install moreutils op Fedora.
  • Ik denk dat het nuttig is om naar de parameter -s te verwijzen. Omdat dat de looptijd van de opdracht weergeeft. Persoonlijk vind ik het leuk om ts en ts -s tegelijkertijd te gebruiken. Ziet er ongeveer zo uit: command | ts -s '(%H:%M:%.S)]' | ts '[%Y-%m-%d %H:%M:%S'. Dit begint als volgt aan de logregels: [2018-12-04 08:31:00 (00:26:28.267126)] Hai <3

Antwoord

Ten eerste, als u verwacht dat deze tijdstempels daadwerkelijk een gebeurtenis vertegenwoordigen, houd er dan rekening mee dat aangezien veel programmas lijnbuffering uitvoeren (sommige agressiever dan andere), het belangrijk is om dit zo dicht mogelijk bij de tijd te zien waarop de oorspronkelijke regel zou zijn afgedrukt in plaats van een tijdstempel van een actie die plaatsvindt.

U kunt ook controleren of uw commando niet al een ingebouwde functie heeft die hiervoor is bedoeld. Als voorbeeld: ping -D bestaat in sommige ping versies, en drukt de tijd sinds het Unix-epoch af voor elke regel. Als uw commando echter geen eigen methode bevat, is er zijn enkele methoden en tools die kunnen worden gebruikt, onder andere:

POSIX shell

Houd in gedachten dat aangezien veel shells hun strings intern opslaan als cstrings, als de invoer de null char acter (\0), kan dit ervoor zorgen dat de regel voortijdig eindigt.

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] ")" 

Reacties

  • Een probleem hier is dat veel programmas worden ingeschakeld zelfs meer outputbuffering als hun stdout een pipe is in plaats van de terminal.
  • @cjm – True. Sommige uitvoerbuffering kan worden verlicht door stdbuf -o 0 te gebruiken, maar als het programma de uitvoerbuffering handmatig afhandelt, krijgt het ‘ t hulp (tenzij er is een optie om de grootte van de uitvoerbuffer uit te schakelen / te verkleinen).
  • Voor python kun je lijnbuffering uitschakelen met python -u
  • @Bwmat No. ... for x in sys.stdin itereert over regels zonder ze eerst allemaal in het geheugen te bufferen.
  • Doe dit en je krijgt buffering … voor een in 1 1 1 1 1; slaap 1; echo; gedaan | python -c ‘ import sys, time; sys.stdout.write (” ” .join ((” ” .join ((time.strftime (” [% Y-% m-% d% H:% M:% S] “, time.gmtime ()), line)) voor regel in sys.stdin))) ‘

Antwoord

Voor een regel-voor-regel deltameting , probeer gnomon .

Het is een opdrachtregelprogramma, een beetje zoals moreutils “s ts, om tijdstempelinformatie toe te voegen aan de standaarduitvoer van een ander commando. Nuttig voor langlopende processen waarbij” je een historisch record wilt “van wat er zo lang duurt.

Alles naar gnomon sturen voeg een tijdstempel toe aan elke regel om aan te geven hoe lang die regel de laatste regel in de buffer was, dat wil zeggen, hoe lang het duurde voordat de volgende regel werd weergegeven. Standaard geeft gnomon de verstreken seconden weer tussen elke regel, maar dat is configureerbaar.

gnomon demo

Reacties

  • Ziet eruit als een geweldig alternatief voor ts bij het gebruik van live processen. Hoewel ts beter geschikt is voor niet-interactieve processen.

Antwoord

Ik had liever hierboven commentaar gegeven, maar dat kan ik niet, reputatieschijnlijk. Hoe dan ook, het bovenstaande Perl-voorbeeld kan als volgt worden ongebufferd:

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

De eerste “$ |” ontbuffert STDOUT. De tweede stelt stderr in als het huidige standaard uitvoerkanaal en ontbuffert het. Aangezien select de oorspronkelijke instelling van $ | retourneert, door de select in een select te wikkelen, zetten we ook $ | terug naar de standaard, STDOUT.

En ja, je kunt knippen en plakken zoals het is. Ik heb het meerdere lijnen gegeven voor leesbaarheid.

En als je echt precies wilt zijn (en je hebt Time :: Hires geïnstalleerd):

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)" 

Reacties

  • Werkt als een charme, zonder dat je niet-standaard pakketten hoeft te installeren.

Antwoord

Ryans bericht geeft een interessant idee, maar het faalt in verschillende opzichten. Tijdens het testen met tail -f /var/log/syslog | xargs -L 1 echo $(date +"[%Y-%m-%d %H:%M:%S]") $1, merkte ik dat het tijdstempel hetzelfde blijft, zelfs als stdout komt later met verschil in seconden uit elkaar. Beschouw deze uitvoer:

[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. 

Mijn voorgestelde oplossing is vergelijkbaar, maar biedt de juiste tijdstempel en gebruikt iets meer draagbare printf in plaats van echo

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

Waarom bash -c "..." bash? Omdat vanwege de -c optie, eerst argumenteren nt wordt toegewezen aan $0 en wordt “niet weergegeven in de uitvoer. Raadpleeg de handleidingpagina van je shell voor de juiste beschrijving van -c

Deze oplossing testen met tail -f /var/log/syslog en (als je zou waarschijnlijk kunnen raden) het verbreken en opnieuw verbinden met mijn wifi, heeft de juiste tijdstempel getoond die wordt geboden door zowel date als syslog berichten

Bash kan worden vervangen door elke bourne-achtige shell, kan worden gedaan met ksh of dash, tenminste die die een -c optie hebben.

Potentiële problemen:

De oplossing vereist xargs, die beschikbaar is op POSIX-compatibele systemen, dus de meeste Unix-achtige systemen moeten worden gedekt. Het zal duidelijk “niet werken als uw systeem niet-POSIX-compatibel is of geen GNU findutils

Antwoord

De meeste antwoorden suggereren om date te gebruiken, maar het is traag genoeg . Als je bash-versie groter is dan 4.2.0, is het beter om printf te gebruiken, het is een ingebouwde bash. Als je oudere bash-versies wilt ondersteunen, kun je een log -functie maken, afhankelijk van de bash-versie:

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 

Zie snelheid verschillen:

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: in plaats van $(date +"${TIMESTAMP_FORMAT}") is het beter om of zelfs $(exec -c date +"${TIMESTAMP_FORMAT}") te versnellen uitvoering.

UPD2: bash 5 biedt EPOCHREALTIME variabele voor microseconden granulariteit, je kunt het gebruiken met dit commando (ongeveer 30% langzamer dan alleen seconden): printf "%(${TIMESTAMP_FORMAT})T.%s %5d %s\n" ${EPOCHREALTIME/./ } $$ "$*"

Reacties

  • Dit antwoord is erg onderschat! Bijzonder mooi om de relatieve prestatie-impact te laten zien.
  • Uitstekende suggestie, maar helaas biedt date geen sub-seconde resolutie, wat in mijn geval een vereiste is
  • Met bash 5.0+ kun je dit commando gebruiken: printf "%(${TIMESTAMP_FORMAT})T.%s %5d %s\n" ${EPOCHREALTIME/./ } $$ "$*"

Antwoord

U kunt dit doen met date en xargs:

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

Uitleg:

xargs -L 1 vertelt xargs om het volgende commando uit te voeren voor elke 1 regel invoer, en het gaat in de eerste regel door terwijl het dit doet. echo `date +"[%Y-%m-%d %H:%M:%S]"` $1 echoot in feite de datum met het invoerargument aan het einde ervan

Opmerkingen

  • De oplossing is dichtbij, maar ‘ t tijdstempel correct als het gaat om uitvoer gescheiden door lange tijdsperioden. Je ‘ gebruikt ook backticks en hebt ‘ niet $1 geciteerd. Dat ‘ is geen goede stijl. Geef altijd de variabelen aan. Bovendien ‘ gebruikt u echo, wat niet draagbaar is. Het ‘ is in orde, maar werkt mogelijk niet goed op sommige systemen.
  • Nadat je dit hebt getest, lijkt het erop dat je ‘ helemaal gelijk hebt … weet je een manier om date elke regel opnieuw worden geëvalueerd, of is het vrijwel hopeloos?

Antwoord

Schaamteloze plug voor iets dat ik schreef net om dit exacte probleem op te lossen: ets , geschreven in Go.

demo

Je kunt veel gebruiksvoorbeelden vinden op de projectpagina.

Het opmerkelijke verschil met bestaande antwoorden en soortgelijke aanbiedingen is dat ets is ontworpen om uw opdracht voor u uit te voeren, in een pty (pseudo tty) – dat wil zeggen, simuleren uw commando wordt native uitgevoerd in een tty. Vergeleken met de uitvoer van een pipingcommando naar bijv. ts, dit maakt tijdstempels meestal transparant, en lost een aantal problemen met piping op:

  • Sommige programmas bufferen agressief wanneer ze naar een pipe schrijven, dus je zie geen output en dan een heleboel output (ja, je kunt ze stdbufen, je kunt zelfs stdbuf en ts in een alias / functie verpakken, maar het zou niet beter zijn als de dingen uit de doos werken);
  • Sommige programmas schakelen kleur en / of interactiviteit uit bij het schrijven naar een pipe;
  • De exitstatus is verdwenen tenzij je pipefail hebt ingeschakeld; etc.

Commandos kunnen direct worden uitgevoerd, wat inhoudt dat u ets eenvoudig kunt toevoegen aan uw bestaande opdrachtregel, of het kunnen shell-opdrachten zijn (zoals weergegeven in het bovenstaande gif). Natuurlijk, als je de uitvoer wilt doorsturen, ets kan dat ook doen.

ets ondersteunt hetzelfde tijdstempelmodi als moreutils ts: absolute tijdmodus, verstreken tijdmodus en incrementele tijdmodus. Het gebruikt betere standaardinstellingen (bijv. Monotone klok wordt altijd gebruikt voor verstreken / incrementele tijdstempels) en heeft extra ondersteuning voor aangepaste tijdzones. Er “een gedetailleerde vergelijking hier .

Nogmaals, https://github.com/zmwangx/ets . Geef het een draai, rapporteer bugs, etc.

Antwoord

Zet dit bij het begin van uw script

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

Of, voor extra krediet, uitvoer naar logbestand 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} ) 

om uit te voeren naar zowel de console als het logbestand:

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 belangrijkste voordelen hiervan zijn om het loggen te scheiden van al het andere, niet de body van het script onoverzichtelijk maken door naar tee of iets dergelijks te pipen bij elke opdracht, en geen aangepaste logboekfuncties te hoeven schrijven en vast te houden aan gewoon oud echo.

Het ts programma is te vinden in het moreutils pakket, dat direct beschikbaar zou moeten zijn onder elk redelijkerwijs gezond administratief regime. 🙂

Geef een reactie

Het e-mailadres wordt niet gepubliceerd. Vereiste velden zijn gemarkeerd met *