it-swarm-eu.dev

Příprava časového razítka na každou řádku výstupu z příkazu

Chci před každou řádek výstupu z příkazu připojit časové razítko. Například:

foo
bar
baz

stal by se

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

... kde čas, který je předponou, je čas, kdy byl řádek vytištěn. Jak toho mohu dosáhnout?

205
anon

moreutils obsahuje ts, což to docela pěkně:

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

To eliminuje potřebu smyčky, každý řádek výstupu bude mít č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 tento server vrátil, restartovali jste se? Prostě běž ping | ts, vyřešen problém: D.

311
Mark McKinstry

Zaprvé, pokud očekáváte, že tato časová razítka budou skutečně představovat událost, mějte na paměti, že protože mnoho programů provádí ukládání do vyrovnávací paměti řádků (některé agresivněji než jiné), je důležité myslet na to tak blízko času, jaký by původní řádek měl byly vytištěny spíše než časové razítko probíhající akce.

Možná také budete chtít zkontrolovat, zda váš příkaz již nemá vestavěnou funkci, která by byla věnována tomuto. Jako příklad, ping -D existuje v některých ping verzích a vytiskne čas od Unixové epochy před každým řádkem. Pokud však váš příkaz neobsahuje vlastní metodu, existuje několik metod a nástrojů, které lze použít, mimo jiné:

POSIX Shell

Mějte na paměti, že protože mnoho skořápek ukládá své řetězce interně jako cstringy, obsahuje-li vstup nulový znak (\0), může to vést k předčasnému ukončení linky.

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] ")'
112
Chris Down

Pro měření delta řádek po řádku zkuste gnomon .

Je to obslužný program příkazového řádku, trochu jako ts moreutils, připojit informace o časovém razítku na standardní výstup jiného příkazu. Užitečné pro dlouhodobé procesy, kde byste chtěli historický záznam toho, co trvá tak dlouho.

Potrubím cokoli do gnomonu se na každý řádek připojí časové razítko, které udává, jak dlouho byla tato linka posledním řádkem ve vyrovnávací paměti - to je, jak dlouho trvalo, než se objevil další řádek. Ve výchozím nastavení gnomon zobrazí sekundy, které uplynuly mezi jednotlivými řádky, ale to je konfigurovatelné.

gnomon demo

47
Janus Troelsen

Ryanův příspěvek sice poskytuje zajímavý nápad, ale v několika ohledech selhává. Při testování s funkcí 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ůstává stejné, i když stdout přichází později s rozdílem v sekundách. 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é, nicméně poskytuje správné časové razítko a používá poněkud více přenosné 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 díky možnosti -c Bude první argument přiřazen k $0 A nebude se zobrazovat ve výstupu. Podrobný popis položky -c Najdete na stránce manuálu společnosti Shell.

Testování tohoto řešení pomocí tail -f /var/log/syslog A (jak jste asi mohli hádat) odpojením a opětovným připojením k mému wifi, ukázalo správné časové razítko poskytované zprávami date a syslog

Bash by mohl být nahrazen jakýmkoli shellem podobným Shellem, mohl by být proveden buď s ksh nebo dash, alespoň s těmi, které mají možnost -c.

Potenciální problémy:

Řešení vyžaduje mít xargs, který je k dispozici v systémech kompatibilních s POSIX, takže by měla být pokryta většina unixových systémů. Je zřejmé, že nebude fungovat, pokud váš systém nepodporuje POSIX nebo nemá GNU findutils

7

Raději bych to komentoval výše, ale nemohu to, pověstně. Vzorek Perl výše však může být bez vyrovnávací paměti takto:

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

První '$ |' unbuffers STDOUT. Druhý nastaví stderr jako aktuální výchozí výstupní kanál a odblokuje jej. Protože select vrací původní nastavení $ |, zabalením výběru do výběru také resetujeme $ | na jeho výchozí, STDOUT.

A ano, můžete vyjmout pastu tak, jak je. Pro větší přehlednost jsem to pro větší přehlednost upravil.

A pokud opravdu chcete získat přesnost (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)'
6
mpersico

Většina odpovědí navrhuje použít date, ale je to dost pomalé. Pokud je vaše verze bash vyšší než 4.2.0, je lepší místo toho použít printf, je to vestavěné bash. Pokud potřebujete podporovat starší verze bash, můžete vytvořit funkci log, která 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 rozdíly v rychlosti:

[email protected]:~$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
[email protected]:~$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: namísto $(date +"${TIMESTAMP_FORMAT}") je lepší použít $(exec date +"${TIMESTAMP_FORMAT}") nebo dokonce $(exec -c date +"${TIMESTAMP_FORMAT}") příliš rychlé provedení.

5
Mikhail

Můžete to 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ům, aby spouští postupovací příkaz pro každý 1 řádek vstupu a předává v prvním řádku, jak to dělá. echo `date +'[%Y-%m-%d %H:%M:%S]'` $1 v podstatě opakuje datum se vstupním argumentem na jeho konci

1
Ryan