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
- Există un utilitar Unix pentru a prepanda marcajele de timp stdin?
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 șiyum 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âtts
, cât șits -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ă.
Comentarii
- Pare o alternativă excelentă la
ts
atunci când se utilizează procese live. În timp cets
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țiecho
, 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.
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. 🙂