Chtěl bych přidat časové razítko na každý řádek výstupu z příkazu. Například:
foo bar baz
by se stalo
[2011-12-13 12:20:38] foo [2011-12-13 12:21:32] bar [2011-12-13 12:22:20] baz
… v současné době prefixed je čas, kdy byl řádek vytištěn. Jak toho mohu dosáhnout?
Komentáře
- Existuje unixový nástroj k předřazení časových značek stdin?
Odpověď
moreutils zahrnuje ts
, což to dělá celkem pěkně:
command | ts "[%Y-%m-%d %H:%M:%S]"
It eliminuje také potřebu smyčky, na každém řádku výstupu bude uvedeno časové razítko.
$ 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
Chcete vědět, kdy se server vrátil, že jste restartovali? Stačí spustit ping | ts
, problém vyřešen: D.
Komentáře
- Jak jsem o tom nevěděl ?!?!?! To úžasně doplňuje ocas -f!
tail -f /tmp/script.results.txt | ts
- pokud nemám ‚ příkaz ts, co mám použít?
- Pokud to ‚ nefunguje, zkuste přesměrovat stderr na stdout, např.
ssh -v 127.0.0.1 2>&1 | ts
- Instalujte provedením
sudo apt install moreutils
na Debianu ayum install moreutils
na Fedora. - Myslím, že poukázat na parametr
-s
je užitečné. Protože to zobrazuje dobu běhu příkazu. Osobně rád používám současněts
its -s
. Vypadá asi takto:command | ts -s '(%H:%M:%.S)]' | ts '[%Y-%m-%d %H:%M:%S'
. Toto předřazuje řádky protokolu takto:[2018-12-04 08:31:00 (00:26:28.267126)] Hai <3
Odpovědět
Za prvé, pokud očekáváte, že tato časová razítka budou skutečně představovat událost, mějte na paměti, že jelikož mnoho programů provádí ukládání linek do vyrovnávací paměti (některé agresivněji než jiné), je důležité si to představit co nejblíže době, kdy by původní řádek byly vytištěny spíše než časové razítko probíhající akce.
Možná budete také chtít zkontrolovat, zda váš příkaz již nemá zabudovanou funkci věnovanou tomu. Příklad: ping -D
existuje v některých verzích ping
a před každým řádkem vytiskne čas od epochy Unixu. Pokud váš příkaz neobsahuje vlastní metodu, je zde existuje několik metod a nástrojů, které lze použít, mimo jiné:
POSIX shell
Mějte na paměti, že protože mnoho shellů ukládá své řetězce interně jako cstrings, pokud vstup obsahuje null char acter (\0
), může to způsobit předčasné ukončení řádku.
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] ")"
Komentáře
- Jedním problémem je, že se zapne mnoho programů ještě více vyrovnávací paměti výstupu, když je jejich výstupem místo terminálu kanál.
- @cjm – True. Některé výstupní vyrovnávací paměti lze zmírnit pomocí
stdbuf -o 0
, ale pokud program ručně zpracovává své výstupní vyrovnávací paměti, nevyhrál ‚ t nápovědu (pokud existuje možnost deaktivovat / zmenšit velikost výstupní vyrovnávací paměti). - U pythonu můžete deaktivovat ukládání do vyrovnávací paměti řádků pomocí
python -u
- @Bwmat No.
... for x in sys.stdin
iteruje přes řádky, aniž by je nejprve ukládal do mezipaměti. - Udělejte to a dostanete vyrovnávací paměť … pro a v 1 1 1 11; spát 1; echo; hotovo | python -c ‚ import sys, time; sys.stdout.write (“ “ .join ((“ “ .join ((time.strftime (“ [%) Y-% m-% d% H:% M:% S] „, time.gmtime ()), řádek)) pro řádek v sys.stdin))) ‚
odpověď
Pro měření delta po řádku , zkuste gnomon .
Je to nástroj příkazového řádku, trochu jako moreutils „s ts, k doplnění informací o časovém razítku na standardní výstup jiného příkazu. Užitečné pro dlouhotrvající procesy, kde byste chtěli historický záznam toho, co to trvá tak dlouho.
Přenesení všeho na gnomon bude před každý řádek předložit časové razítko, které označuje, jak dlouho byl tento řádek posledním řádkem ve vyrovnávací paměti – tedy jak dlouho trvalo, než se objevil další řádek. Ve výchozím nastavení gnomon zobrazí uplynulé sekundy mezi jednotlivými řádky, ale to je konfigurovatelné.
Komentáře
- Při použití živých procesů to vypadá jako skvělá alternativa k
ts
. Zatímcots
je vhodnější pro neinteraktivní procesy.
Odpovědět
Chtěl bych raději komentovat výše, ale nemohu to „reputačně. Každopádně výše uvedený vzorek Perlu lze bez vyrovnávací paměti zrušit takto:
command | perl -pe "use POSIX strftime; $|=1; select((select(STDERR), $| = 1)[0]); print strftime "[%Y-%m-%d %H:%M:%S] ", localtime"
The first „$ |“ unbuffers STDOUT. The second one sets stderr as the current default output channel and unbuffes it. Vzhledem k tomu, select vrátí původní nastavení $ |, zabalením select uvnitř select, také resetujeme $ | na výchozí STDOUT.
A ano, můžete je vložit tak, jak jsou. Pro lepší čitelnost jsem to vložil do několika řádků.
A pokud opravdu chcete dosáhnout přesnosti (a máte Time :: Hires nainstalován):
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)"
Komentáře
- Funguje to jako kouzlo, aniž byste museli instalovat nestandardní balíčky.
Odpověď
Ryanův příspěvek poskytuje zajímavý nápad, ale v několika ohledech selhává. Při testování pomocí tail -f /var/log/syslog | xargs -L 1 echo $(date +"[%Y-%m-%d %H:%M:%S]") $1
jsem si všiml, že časové razítko zůstane stejné, i když stdout
přichází později s rozdílem v sekundových intervalech. Zvažte tento výstup:
[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.
Moje navrhované řešení je podobné, ale poskytuje správné časové razítko a používá o něco přenosnější printf
spíše než echo
| xargs -L 1 bash -c "printf "[%s] %s\n" "$(date +%Y-%m-%d\ %H:%M:%S )" "$*" " bash
Proč bash -c "..." bash
? Protože kvůli možnosti -c
nejprve argumentovat nt nebude přiřazen k $0
a nebude se zobrazovat na výstupu. Správný popis -c
Testování tohoto řešení pomocí tail -f /var/log/syslog
a (jako pravděpodobně byste mohli hádat) odpojení a opětovné připojení k mé wifi, ukázalo správné časové razítko poskytované jak date
, tak i syslog
zprávami
Bash může být nahrazen jakýmkoli bourne-like shellem, může být provedeno buď ksh
nebo dash
, alespoň těmi které mají možnost -c
.
Potenciální problémy:
Řešení vyžaduje mít xargs
, který je k dispozici na systémech kompatibilních s POSIX, takže většina systémů podobných Unixu by měla být pokryta. Je zřejmé, že nebude fungovat, pokud váš systém není kompatibilní s POSIXem nebo nemá GNU findutils
Odpověď
Většina odpovědí navrhuje použít date
, ale je to dost pomalé . Pokud je vaše verze bash větší než 4.2.0, je lepší použít místo toho printf
, je to vestavěný bash. Pokud potřebujete podporovat starší verze bash, můžete vytvořit log
funkce závisí na verzi 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
Zobrazit rychlost rozdíly:
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: místo $(date +"${TIMESTAMP_FORMAT}")
je lepší použít $(exec date +"${TIMESTAMP_FORMAT}")
nebo dokonce $(exec -c date +"${TIMESTAMP_FORMAT}")
příliš rychlé provedení.
UPD2: bash 5 poskytuje EPOCHREALTIME
proměnnou pro mikrosekundy granularity, you can use it by this command (about 30% slower than seconds only): printf "%(${TIMESTAMP_FORMAT})T.%s %5d %s\n" ${EPOCHREALTIME/./ } $$ "$*"
Komentáře
- Tato odpověď je velmi podceňovaná! Obzvláště pěkné je ukázat relativní dopad na výkon.
- Skvělý návrh, ale bohužel datum neposkytuje subsekundové rozlišení, což je v mém případě požadavek
- S bash 5.0+ můžete použít tento příkaz:
printf "%(${TIMESTAMP_FORMAT})T.%s %5d %s\n" ${EPOCHREALTIME/./ } $$ "$*"
Odpovědět
To lze provést pomocí date
a xargs
:
... | xargs -L 1 echo `date +"[%Y-%m-%d %H:%M:%S]"` $1
Vysvětlení:
xargs -L 1
řekne xargs, aby spustil pokračující příkaz pro každý 1 řádek vstupu, a ten prochází v prvním řádku, když to dělá. echo `date +"[%Y-%m-%d %H:%M:%S]"` $1
v zásadě odráží datum se vstupním argumentem na konci
Komentáře
- Řešení je blízko, ale ‚ t časové razítko správně, pokud jde o výstup oddělené dlouhými časovými obdobími. Také ‚ používáte zpětné lístky a ‚ neuvádíte
$1
. To ‚ není dobrý styl. Vždy uvádějte proměnné. Kromě toho ‚ používáteecho
, které není přenosné. Je ‚ v pořádku, ale v některých systémech nemusí správně fungovat. - Po vyzkoušení se zdá, že máte ‚ naprostou pravdu … víte o nějakém způsobu, jak
date
nechat přehodnotit každý řádek, nebo je to do značné míry beznadějné?
odpověď
nestydatá zástrčka pro něco, co právě jsem napsal k vyřešení tohoto přesného problému: ets
, napsáno v Go.
Na stránce projektu najdete mnoho příkladů použití.
Pozoruhodný rozdíl oproti stávajícím odpovědím a podobným nabídkám spočívá v tom, že ets
je navržen tak, aby za vás spustil váš příkaz v pty (pseudo tty) – tedy simulaci váš příkaz běží nativně v tty. Ve srovnání s výstupem příkazu potrubí do např. ts
, díky tomu je časová značka většinou transparentní a řeší spoustu problémů s pipováním:
- Některé programy se při zápisu do kanálu agresivně vyrovnávají, takže nevidíte žádný výstup a pak spoustu výstupů (jo, můžete je stdbuf, můžete dokonce zabalit stdbuf a ts do aliasu / funkce, ale nebylo by lepší, kdyby věci fungovaly z krabice);
- Některé programy zakazují při zápisu do kanálu barvu nebo interaktivitu;
- Stav ukončení je pryč, pokud nezapnete pipefail atd.
Příkazy mohou buďte přímo „ed“, což znamená, že můžete jednoduše přidat ets
do svého stávajícího příkazového řádku, nebo to mohou být příkazy prostředí (jak je ukázáno výše v gifu). Samozřejmě, pokud chcete zapojit výstup, ets
to může také udělat.
ets
podporuje to samé režimy časových značek jako moreutils ts
: režim absolutního času, režim uplynulého času a režim přírůstkového času. Používá výchozí nastavení (např. Monotónní hodiny se vždy používají pro uplynulá / přírůstková časová razítka) a má další podporu pro vlastní časová pásma. Zde je podrobné srovnání zde .
Opět https://github.com/zmwangx/ets . Roztočte to, nahlaste chyby atd.
Odpovědět
Umístit toto blízko začátku vašeho skriptu
# prepend a timestamp to all output exec &> >( ts "%Y-%m-%d.%H%M.%.S " )
Nebo, pro extra kredit, výstup do souboru protokolu pomocí:
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} )
pro výstup do konzoly i do souboru protokolu:
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} )
Hlavními výhodami tohoto postupu je oddělit protokolování od všeho jiného, nikoli přeplnění těla skriptu pipováním na tee
nebo podobné u každého příkazu a bez nutnosti psát vlastní funkce protokolování a dodržování obyčejných starých echo
.
Program ts
se nachází v balíčku moreutils
, který by měl být snadno dostupný za rozumných podmínek rozumný režim správy. 🙂