Prependarea unui timestamp la fiecare linie de ieșire dintr-o comandă

Aș dori să prepend o timestamp la fiecare linie de ieșire dintr-o comandă. De exemplu:

foo bar baz 

ar deveni

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

… unde este momentul prefixat este ora la care a fost tipărită linia. Cum pot realiza acest lucru?

Comentarii

Răspuns

moreutils include ts care face acest lucru destul de frumos:

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

elimină și necesitatea unei bucle, fiecare linie de ieșire va avea un timestamp pus pe ea.

$ 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 

Vrei să știi când a revenit serverul pe care l-ai repornit? Rulați doar ping | ts, problema rezolvată: D.

Comentarii

  • Cum nu știu despre asta ?!?!?! Acest lucru completează uluitor -f uimitor! tail -f /tmp/script.results.txt | ts
  • dacă nu am ‘ comanda ts, ce ar trebui să folosesc?
  • Dacă ‘ nu funcționează, încercați să redirecționați stderr către stdout de ex. ssh -v 127.0.0.1 2>&1 | ts
  • Instalați făcând sudo apt install moreutils pe Debian și yum install moreutils pe Fedora.
  • Cred că indicarea parametrului -s este utilă. Pe măsură ce afișează timpul de rulare al comenzii. Personal îmi place să folosesc atât ts, cât și ts -s în același timp. Arată cam așa: command | ts -s '(%H:%M:%.S)]' | ts '[%Y-%m-%d %H:%M:%S'. Aceasta previne liniile de jurnal astfel: [2018-12-04 08:31:00 (00:26:28.267126)] Hai <3

Răspuns

În primul rând, dacă vă așteptați ca aceste timestamp-uri să reprezinte de fapt un eveniment, rețineți că, din moment ce multe programe efectuează buffering de linie (unele mai agresiv decât altele), este important să vă gândiți la acest lucru cât mai aproape de momentul au fost tipărite mai degrabă decât un timestamp al unei acțiuni care se desfășoară.

De asemenea, vă recomandăm să verificați dacă comanda dvs. nu are deja o caracteristică încorporată dedicată acestui lucru. De exemplu, ping -D există în unele versiuni ping și tipărește timpul de la epoca Unix înainte de fiecare linie. Dacă comanda dvs. nu conține propria metodă, totuși, există sunt câteva metode și instrumente care pot fi folosite, printre altele:

POSIX shell

Rețineți că, din moment ce multe shell-uri își stochează șirurile de caractere intern sub formă de șiruri de caractere, dacă intrarea conține valoarea nulă char acter (\0), poate cauza terminarea prematură a liniei.

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

Comentarii

  • O problemă aici este că multe programe pornesc tamponare de ieșire și mai mare atunci când stdout-ul lor este o conductă în locul terminalului.
  • @cjm – Adevărat. Unele buffere de ieșire pot fi ameliorate utilizând stdbuf -o 0, dar dacă programul gestionează manual bufferul de ieșire, nu va primi ‘ t help (cu excepția cazului în care există o opțiune pentru a dezactiva / reduce dimensiunea bufferului de ieșire).
  • Pentru python, puteți dezactiva bufferingul de linie cu python -u
  • @Bwmat No. ... for x in sys.stdin iterează peste linii fără a le memora mai întâi pe toate în memorie.
  • Faceți acest lucru și veți obține buffering … pentru un în 1 1 1 1 1; dormi 1; ecou; gata | python -c ‘ import sys, time; sys.stdout.write (” ” .join ((” ” .join ((time.strftime (” [% Y-% m-% d% H:% M:% S] „, time.gmtime ()), line)) pentru linie în sys.stdin))) ‘

Răspuns

Pentru o măsurare delta linie cu linie , încercați gnomon .

Este un utilitar de linie de comandă, un pic ca moreutils „s ts, pentru a prepara informațiile de timestamp la ieșirea standard a unei alte comenzi. Utilă pentru procesele de lungă durată în care„ doriți o înregistrare istorică a ceea ce durează atât de mult.

pregătiți o marcă de timp pentru fiecare linie, indicând cât timp a fost ultima linie din buffer – adică cât a durat următoarea linie să apară. În mod implicit, gnomon va afișa secundele trecute între fiecare linie, dar aceasta este configurabilă.

demonstrație gnomon

Comentarii

  • Pare o alternativă excelentă la ts atunci când se utilizează procese live. În timp ce ts este mai potrivit pentru procesele non-interactive.

Răspuns

Aș fi preferat să comentez mai sus, dar nu pot, din punct de vedere reputațional. Oricum, eșantionul Perl de mai sus nu poate fi tamponat după cum urmează:

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

primul „$ |” unbuffers STDOUT. Al doilea setează stderr ca canal de ieșire implicit curent și îl anulează. Deoarece select returnează setarea inițială de $ |, prin înfășurarea selectului în interiorul unei selectări, resetăm $ | la valoarea implicită, STDOUT.

Și da, puteți tăia „n paste așa cum este. L-am compus din mai multe linii pentru lizibilitate.

Și dacă doriți cu adevărat să obțineți precizie (și aveți instalat Time :: Hires ):

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

Comentarii

  • Funcționează ca un farmec, fără a fi nevoie să instalați pachete non-standard.

Răspuns

Postarea lui Ryan oferă o idee interesantă, cu toate acestea, eșuează în mai multe privințe. În timp ce testam cu tail -f /var/log/syslog | xargs -L 1 echo $(date +"[%Y-%m-%d %H:%M:%S]") $1, am observat că marcajul de timp rămâne același chiar dacă stdout vine mai târziu cu diferență în secunde distanță. Luați în considerare această ieșire:

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

Soluția propusă de mine este similară, totuși oferă un timp corect și folosește ceva mai portabil printf mai degrabă decât echo

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

De ce bash -c "..." bash? Deoarece, datorită opțiunii -c, argumentați mai întâi nt este atribuit $0 și nu va apărea în rezultat. Consultați pagina manuală a shell-ului pentru descrierea corectă a -c

Testarea acestei soluții cu tail -f /var/log/syslog și (ca probabil ați putea ghici) deconectarea și reconectarea la wifi-ul meu, a arătat marcarea corectă a timpului oferită atât de date, cât și de syslog mesaje

Bash ar putea fi înlocuit cu orice shell de tip bourne, ar putea fi făcut fie cu ksh, fie cu dash, cel puțin care au opțiunea -c.

Probleme potențiale:

Soluția necesită xargs, care este disponibil pe sistemele compatibile POSIX, deci majoritatea sistemelor similare Unix ar trebui acoperite. Evident, nu va funcționa dacă sistemul dvs. nu este compatibil POSIX sau nu are GNU findutils

Răspuns

Majoritatea răspunsurilor sugerează utilizarea date, dar este suficient de lent . Dacă versiunea dvs. bash este mai mare decât 4.2.0 este mai bine să utilizați printf în schimb, este o versiune bash builtin. Dacă trebuie să acceptați versiuni bash vechi, puteți crea funcția log depinde de versiunea bash:

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 

Vedeți viteza diferențe:

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: în loc de $(date +"${TIMESTAMP_FORMAT}") este mai bine să folosiți $(exec date +"${TIMESTAMP_FORMAT}") sau chiar $(exec -c date +"${TIMESTAMP_FORMAT}") execuție prea rapidă.

UPD2: bash 5 oferă variabila EPOCHREALTIME pentru microsecunde granularitate, o puteți utiliza cu această comandă (cu aproximativ 30% mai lent decât doar câteva secunde): printf "%(${TIMESTAMP_FORMAT})T.%s %5d %s\n" ${EPOCHREALTIME/./ } $$ "$*"

Comentarii

  • Acest răspuns este foarte subevaluat! Este deosebit de plăcut pentru a arăta impactul relativ asupra performanței.
  • Sugestie excelentă, dar din păcate data nu oferă rezoluție sub-secundă, ceea ce este o cerință în cazul meu
  • Cu bash 5.0+ puteți utiliza această comandă: printf "%(${TIMESTAMP_FORMAT})T.%s %5d %s\n" ${EPOCHREALTIME/./ } $$ "$*"

Răspuns

Puteți face acest lucru cu date și xargs:

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

Explicație:

xargs -L 1 spune xargs să execute comanda de procedură pentru fiecare linie de intrare și trece în prima linie în timp ce o face. echo `date +"[%Y-%m-%d %H:%M:%S]"` $1, în principiu, repetă data cu argumentul de intrare la sfârșitul acesteia

Comentarii

  • Soluția este apropiat, dar nu ‘ este timestamp corect atunci când vine vorba de ieșire separat de perioade lungi de timp. De asemenea, ‘ utilizați backticks și nu ați ‘ citat $1. ‘ nu este un stil bun. Citați întotdeauna variabilele. În plus, ‘ utilizați echo, care nu este portabil. ‘ este în regulă, dar este posibil să nu funcționeze corect pe unele sisteme.
  • După testare, se pare că ‘ ai absolut dreptate … știi vreun mod de a face date fi reevaluat fiecare linie sau este destul de lipsit de speranță?

Răspunde

Fișă nerușinată pentru ceva I tocmai am scris pentru a rezolva această problemă exactă: ets , scris în Go.

demo

Puteți găsi o mulțime de exemple de utilizare pe pagina proiectului.

Diferența notabilă față de răspunsurile existente și oferte similare este că ets este conceput pentru a rula comanda pentru dvs., într-un pty (pseudo tty) – adică simulând comanda ta rulează nativ în tty. Comparativ cu ieșirea comenzii de conducte în ex. ts, acest lucru face ca marcarea temporală să fie în cea mai mare parte transparentă și rezolvă o grămadă de probleme legate de conducte:

  • Unele programe tamponează agresiv când scrieți pe o conductă, astfel încât să nu vedeți nicio ieșire și apoi o grămadă de ieșire (da, le puteți stdbuf, puteți chiar să înfășurați stdbuf și ts într-un alias / funcție, dar nu ar fi mai bine dacă lucrurile funcționează din cutie);
  • Unele programe dezactivează culoarea și / sau interactivitatea atunci când scrieți într-o conductă;
  • Starea de ieșire nu mai există decât dacă ați activat pipefail-ul; etc.

Comenzile pot fii direct executat, adică poți pur și simplu să prepari ets liniei de comandă existente sau pot fi comenzi shell (așa cum se arată în gif de mai sus). Desigur, dacă doriți să introduceți ieșirea în conductă, ets poate face și asta.

ets acceptă același lucru moduri timestamp ca moreutils ts: modul timp absolut, modul timp scurs și modul timp incremental. Utilizează valorile implicite saner (de exemplu, ceasul monoton este întotdeauna utilizat pentru timestamp-urile scurs / incrementale) și are suport suplimentar pentru fusurile orare personalizate. Există „o comparație detaliată aici .

Din nou, https://github.com/zmwangx/ets . Dă-i o rotație, raportează erori etc.

Răspunde

Pune acest lucru aproape de începutul scriptului dvs.

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

Sau, pentru credit suplimentar, ieșiți în fișierul jurnal prin:

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

pentru a ieși atât în consolă, cât și în fișierul jurnal:

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

Principalele avantaje ale acestui lucru sunt separarea înregistrării de toate celelalte, nu aglomerați corpul scriptului prin canalizare către tee sau similar la fiecare comandă și nu trebuie să scrieți funcții de înregistrare personalizate și să rămâneți la echo.

Programul ts se găsește în pachetul moreutils, care ar trebui să fie disponibil în orice mod rezonabil regim de administrare sănătos. 🙂

Lasă un răspuns

Adresa ta de email nu va fi publicată. Câmpurile obligatorii sunt marcate cu *