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.


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

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


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


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

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.


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


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
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.