Anexando um carimbo de data e hora a cada linha de saída de um comando

Respostas:

274

O moreutils inclui o tsque faz isso muito bem:

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

Também elimina a necessidade de um loop, cada linha de saída terá um carimbo de data / hora.

$ 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

Você quer saber quando o servidor voltou, você reiniciou? Apenas corra ping | ts, problema resolvido: D.

Mark McKinstry
fonte
8
Como eu não sabia disso?!?!?! Isso complementa -f incrivelmente! tail -f /tmp/script.results.txt | ts
de Bruno Bronosky
E o cygwin? Existe algo parecido? Não parece que os joelhos de Joey estão lá.
CrazyPenguin
3
se eu não tiver o comando ts, o que devo usar?
ekassis
1
Se ele não está funcionando, tente redirecionando por exemplossh -v 127.0.0.1 2>&1 | ts
jchook
3
Eu acho que apontar o parâmetro -sé útil. Como isso exibe o tempo de execução do comando. Pessoalmente, gosto de usar os dois tse ts -sao mesmo tempo. É algo como isto: command | ts -s '(%H:%M:%.S)]' | ts '[%Y-%m-%d %H:%M:%S'. Isso precede as linhas de log da seguinte maneira:[2018-12-04 08:31:00 (00:26:28.267126)] Hai <3
BrainStone
100

Em primeiro lugar, se você espera que esses registros de data e hora realmente representem um evento, lembre-se de que, como muitos programas executam buffer de linha (alguns de forma mais agressiva que outros), é importante pensar nisso o mais próximo possível do tempo que a linha original teria. foi impresso em vez de um carimbo de data e hora de uma ação em andamento.

Você também pode verificar se o seu comando ainda não possui um recurso embutido dedicado a isso. Como exemplo, ping -Dexiste em algumas pingversões e imprime o tempo desde a época do Unix antes de cada linha. Se seu comando não contiver seu próprio método, no entanto, existem alguns métodos e ferramentas que podem ser empregados, entre outros:

Shell POSIX

Lembre-se de que, como muitos shells armazenam suas seqüências internamente como cstrings, se a entrada contiver o caractere nulo ( \0), isso poderá fazer com que a linha termine prematuramente.

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'

Pitão

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

Rubi

command | ruby -pe 'print Time.now.strftime("[%Y-%m-%d %H:%M:%S] ")'
Chris Down
fonte
3
Um problema aqui é que muitos programas ativam ainda mais buffer de saída quando o stdout é um pipe em vez do terminal.
Cjm
3
@cjm - Verdadeiro. Alguns buffers de saída podem ser aliviados usando stdbuf -o 0, mas se o programa estiver manipulando manualmente seu buffer de saída, isso não ajudará (a menos que haja uma opção para desativar / reduzir o tamanho do buffer de saída).
Chris Baixo
2
Para python, você pode desativar o buffer de linha compython -u
ibizaman 21/11
@ No. Bwmat ... for x in sys.stdinitera sobre linhas sem armazená -las todas na memória primeiro.
Chris Baixo
Faça isso e você obtém buffer ... para um em 1 1 1 1 1; dorme 1; eco; feito | python -c 'import sys, time; sys.stdout.write ("". join (("" .join ((time.strftime ("[% Y-% m-% d% H:% M:% S]] ", time.gmtime ()), linha)) para a linha em sys.stdin))) '
ChuckCottrill 4/18/18
41

Para uma medição delta linha a linha, tente o gnomon .

É um utilitário de linha de comando, um pouco como o ts de moreutils, para acrescentar informações de registro de data e hora à saída padrão de outro comando. Útil para processos de longa execução, nos quais você deseja um registro histórico do que está demorando tanto.

A canalização de qualquer coisa para o gnomon precederá um carimbo de data / hora para cada linha, indicando quanto tempo essa linha foi a última linha no buffer - ou seja, quanto tempo levou para que a próxima linha aparecesse. Por padrão, o gnomon exibirá os segundos decorridos entre cada linha, mas isso é configurável.

demo gnomon

Janus Troelsen
fonte
Parece uma ótima alternativa para tsusar processos ao vivo. Embora tsseja mais adequado para processos não interativos.
BrainStone
7

O post de Ryan fornece uma ideia interessante, no entanto, falha em vários aspectos. Ao testar com tail -f /var/log/syslog | xargs -L 1 echo $(date +'[%Y-%m-%d %H:%M:%S]') $1 , notei que o carimbo de data / hora permanece o mesmo, mesmo que stdoutvenha mais tarde com diferença de segundos. Considere esta saída:

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

Minha solução proposta é semelhante, mas fornece registro de data e hora adequado e usa um pouco mais portátil em printfvez deecho

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

Por que bash -c '...' bash? Porque devido à -copção, o primeiro argumento é atribuído $0e não aparece na saída. Consulte a página de manual do seu shell para obter a descrição adequada de-c

Testar esta solução com tail -f /var/log/sysloge (como você provavelmente poderia adivinhar) desconectar e reconectar-se ao meu wifi mostrou o registro de data e hora adequado fornecido por ambos datee por syslogmensagens

O Bash pode ser substituído por qualquer casco tipo bourne, pode ser feito com um kshou dash, pelo menos, aqueles que têm -copção.

Questões potenciais:

A solução exige ter xargs, disponível em sistemas compatíveis com POSIX, portanto, a maioria dos sistemas semelhantes ao Unix deve ser coberta. Obviamente, não funcionará se o seu sistema não for compatível com POSIX ou não tiverGNU findutils

Sergiy Kolodyazhnyy
fonte
5

Eu preferiria comentar acima, mas não posso, reputacionalmente. De qualquer forma, a amostra Perl acima pode ser inalterada da seguinte maneira:

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

O primeiro '$ |' abafadores STDOUT. O segundo define o stderr como o canal de saída padrão atual e o armazena em buffer. Como select retorna a configuração original de $ |, envolvendo o select dentro de um select, também redefinimos $ | para o padrão, STDOUT.

E sim, você pode cortar e colar como está. Eu multi-alinhado para legibilidade.

E se você realmente quer ser preciso (e você tem o Time :: Hires instalado):

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)'
mpersico
fonte
1
Funciona como um encanto, sem precisar instalar nenhum pacote não padrão.
Jay Taylor
2

A maioria das respostas sugere usar date, mas é lenta o suficiente. Se sua versão do bash for maior que a 4.2.0, é melhor usá- printflo, é um bash embutido. Se você precisar oferecer suporte a versões legadas do bash, poderá criar a logfunção depende da versão do 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

Veja as diferenças de velocidade:

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: em vez de $(date +"${TIMESTAMP_FORMAT}")ser melhor usar $(exec date +"${TIMESTAMP_FORMAT}")ou até $(exec -c date +"${TIMESTAMP_FORMAT}")acelerar a execução.

Mikhail
fonte
0

Você pode fazer isso com datee xargs:

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

Explicação:

xargs -L 1diz ao xargs para executar o comando de prosseguimento para cada 1 linha de entrada e passa na primeira linha ao fazê-lo. echo `date +'[%Y-%m-%d %H:%M:%S]'` $1basicamente ecoa a data com o argumento de entrada no final

Ryan
fonte
2
A solução está próxima, mas não timestamp corretamente quando se trata de saída separada por longos períodos de tempo. Além disso, você está usando backticks e não citou $1. Isso não é bom estilo. Sempre cite as variáveis. Além disso, você está usando echo, o que não é portátil. Está tudo bem, mas pode não funcionar corretamente em alguns sistemas.
Sergiy Kolodyazhnyy
Depois de testar isso, parece que você está absolutamente certo ... você conhece alguma maneira de fazer com que dateseja reavaliado a cada linha, ou é praticamente impossível?
Ryan