Desejo anexar um carimbo de data / hora a cada linha de saída de um comando. Por exemplo:
foo bar baz
se tornaria
[2011-12-13 12:20:38] foo [2011-12-13 12:21:32] bar [2011-12-13 12:22:20] baz
… onde o tempo estiver prefixado é a hora em que a linha foi impressa. Como posso fazer isso?
Comentários
- Existe um utilitário Unix para preceder carimbos de data / hora stdin?
Resposta
moreutils inclui ts
que faz isso muito bem:
command | ts "[%Y-%m-%d %H:%M:%S]"
É elimina a necessidade de um loop também, cada linha de saída terá um carimbo de data / hora colocado nela.
$ 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
Quer saber quando esse servidor voltou a funcionar, você reiniciou? Basta executar ping | ts
, problema resolvido: D.
Comentários
- Como eu não sabia disso ?!?!?! Isso complementa o tail -f surpreendentemente!
tail -f /tmp/script.results.txt | ts
- se eu não ‘ não tiver o comando ts, o que devo usar?
- Se ‘ não estiver funcionando, tente redirecionar stderr para stdout, por exemplo
ssh -v 127.0.0.1 2>&1 | ts
- Instale fazendo
sudo apt install moreutils
no Debian eyum install moreutils
no Fedora. - Acho que apontar o parâmetro
-s
é útil. Como isso exibe o tempo de execução do comando. Pessoalmente, gosto de usarts
ets -s
ao mesmo tempo. Tem a seguinte aparência:command | ts -s '(%H:%M:%.S)]' | ts '[%Y-%m-%d %H:%M:%S'
. Isso precede as linhas de registro como esta:[2018-12-04 08:31:00 (00:26:28.267126)] Hai <3
Resposta
Em primeiro lugar, se você está esperando que esses timestamps representem realmente um evento, tenha em mente que, uma vez que muitos programas realizam buffer de linha (alguns mais agressivamente do que outros), é importante pensar nisso como próximo ao tempo que a linha original faria foram impressos em vez de um carimbo de data / hora de uma ação em andamento.
Você também pode querer verificar se o seu comando ainda não tem um recurso embutido dedicado a fazer isso. Por exemplo, ping -D
existe em algumas ping
versões e imprime a hora desde a época do Unix antes de cada linha. Se o seu comando não contém seu próprio método, no entanto, há são alguns métodos e ferramentas que podem ser empregados, entre outros:
POSIX shell
Tenha em mente que, como muitos shells armazenam suas strings internamente como cstrings, se a entrada contiver o nulo Caracteres acter (\0
), pode 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"
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] ")"
Comentários
- Um problema aqui é que muitos programas ligam ainda mais buffer de saída quando seu stdout é um pipe em vez do terminal.
- @cjm – True. Algum buffer de saída pode ser aliviado usando
stdbuf -o 0
, mas se o programa está lidando manualmente com seu buffer de saída, ele não ‘ ajuda (a menos existe uma opção para desativar / reduzir o tamanho do buffer de saída). - Para python, você pode desativar o buffer de linha com
python -u
- @Bwmat No.
... for x in sys.stdin
itera sobre as linhas sem armazená-las em buffer primeiro na memória. - Faça isso e você obterá o armazenamento em buffer … para um em 1 1 1 1 1; dormir 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 linha em sys.stdin))) ‘
Resposta
Para uma medição delta linha por linha , tente gnomon .
É um utilitário de linha de comando, um pouco como moreutil “s ts, para anexar informações de carimbo de data / hora à saída padrão de outro comando. Útil para processos de longa execução onde você gostaria de um registro histórico do que está demorando tanto.
Canalizar qualquer coisa para o gnomon irá prefixe um carimbo de data / hora em cada linha, indicando quanto tempo essa linha foi a última linha no buffer – isto é, quanto tempo levou para a próxima linha aparecer. Por padrão, o gnomon exibirá os segundos decorridos entre cada linha, mas isso é configurável.
Comentários
- Parece uma ótima alternativa para
ts
ao usar processos ativos. Emborats
seja mais adequado para processos não interativos.
Resposta
Eu teria preferido comentar acima, mas não posso, em termos de reputação. De qualquer forma, o exemplo de Perl acima pode ser desempacotado da seguinte forma:
command | perl -pe "use POSIX strftime; $|=1; select((select(STDERR), $| = 1)[0]); print strftime "[%Y-%m-%d %H:%M:%S] ", localtime"
O primeiro “$ |” remove o buffer de STDOUT. O segundo define stderr como o canal de saída padrão atual e o remove de buffer. Como select retorna a configuração original de $ |, envolvendo o select dentro de um select, também redefinimos $ | para seu padrão, STDOUT.
E sim, você pode cortar “e colar como está. Eu alinhei várias linhas para maior legibilidade.
E se você realmente quiser ser preciso (e você tem 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)"
Comentários
- Funciona perfeitamente, sem ter que instalar nenhum pacote fora do padrão.
Resposta
Postagem 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
, percebi que o carimbo de data / hora permanece o mesmo, mesmo se stdout
vem depois com diferença em 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, no entanto, fornece a marcação de tempo adequada e usa um pouco mais portátil printf
em vez de echo
| xargs -L 1 bash -c "printf "[%s] %s\n" "$(date +%Y-%m-%d\ %H:%M:%S )" "$*" " bash
Por que bash -c "..." bash
? Porque devido à opção -c
, primeiro argumento nt é atribuído a $0
e não aparecerá 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/syslog
e (como você provavelmente poderia adivinhar) desconectando e reconectando ao meu wi-fi, mostrou a marcação de tempo adequada fornecida pelas mensagens date
e syslog
p>
O Bash pode ser substituído por qualquer shell semelhante ao bourne, pode ser feito com ksh
ou dash
, pelo menos aqueles que têm -c
opção.
Problemas potenciais:
A solução requer xargs
, que está disponível em sistemas compatíveis com POSIX, portanto, a maioria dos sistemas semelhantes ao Unix devem ser cobertos. Obviamente, não funcionará se o seu sistema não for compatível com POSIX ou não tiver GNU findutils
Resposta
A maioria das respostas sugere o uso de date
, mas é lento o suficiente . Se sua versão do bash for maior que 4.2.0, é melhor usar printf
em vez disso, é um bash embutido. Se você precisa oferecer suporte a versões legadas do bash, você pode criar log
função dependendo 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
Ver velocidade diferenças:
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}")
, é “melhor usar $(exec date +"${TIMESTAMP_FORMAT}")
ou mesmo $(exec -c date +"${TIMESTAMP_FORMAT}")
para acelerar a execução.
UPD2: o bash 5 fornece EPOCHREALTIME
variável para microssegundos granularidade, você pode usá-lo por este comando (cerca de 30% mais lento do que segundos apenas): printf "%(${TIMESTAMP_FORMAT})T.%s %5d %s\n" ${EPOCHREALTIME/./ } $$ "$*"
Comentários
- Essa resposta é muito subestimada! Particularmente bom para mostrar o impacto relativo no desempenho.
- Excelente sugestão, mas infelizmente a data não fornece resolução inferior a um segundo, o que é um requisito no meu caso
- Com o bash 5.0+ você pode usar este comando:
printf "%(${TIMESTAMP_FORMAT})T.%s %5d %s\n" ${EPOCHREALTIME/./ } $$ "$*"
Resposta
Você pode fazer isso com date
e xargs
:
... | xargs -L 1 echo `date +"[%Y-%m-%d %H:%M:%S]"` $1
Explicação:
xargs -L 1
diz ao xargs para executar o comando a seguir para cada 1 linha de entrada, e ele passa na primeira linha ao fazer isso. echo `date +"[%Y-%m-%d %H:%M:%S]"` $1
basicamente ecoa a data com o argumento de entrada no final dela
Comentários
- A solução está próximo, mas não ‘ t marca de data e hora corretamente quando se trata de saída separada por longos períodos de tempo. Além disso, você ‘ está usando crases e ‘ t citou
$1
. Esse ‘ não é um bom estilo. Sempre cite as variáveis. Além disso, você ‘ está usandoecho
, que não é portátil. É ‘ tudo bem, mas pode não funcionar corretamente em alguns sistemas. - Depois de testar isso, parece que você ‘ está absolutamente certo … você conhece alguma maneira de fazer
date
seja reavaliado a cada linha, ou é praticamente inútil?
Resposta
Plug desavergonhado para algo I acabei de escrever para resolver esse problema exato: ets
, escrito em Go.
Você pode encontrar muitos exemplos de uso na página do projeto.
A diferença notável das respostas existentes e ofertas semelhantes é que ets
foi projetado para executar seu comando para você, em um pty (pseudo tty) – ou seja, simulando seu comando rodando nativamente em um tty. Em comparação com a saída do comando de tubulação em, por exemplo, ts
, isso torna o timestamping mais transparente e resolve vários problemas de piping:
- Alguns programas armazenam em buffer agressivamente ao gravar em um pipe, então você não vê nenhuma saída e, em seguida, um monte de saída (sim, você pode stdbuf-los, você pode até envolver stdbuf e ts em um alias / função, mas não seria melhor se as coisas funcionassem imediatamente);
- Alguns programas desativam a cor e / ou interatividade ao gravar em um tubo;
- O status de saída desaparece, a menos que você ligue o pipefail; etc.
Os comandos podem ser executado diretamente, o que significa que você pode simplesmente acrescentar ets
à sua linha de comando existente ou podem ser comandos do shell (como mostrado no gif acima). Claro, se você quiser canalizar a saída, ets
pode fazer isso também.
ets
suporta o mesmo modos de registro de data e hora como moreutils ts
: modo de tempo absoluto, modo de tempo decorrido e modo de tempo incremental. Ele usa padrões mais corretos (por exemplo, relógio monotônico é sempre usado para carimbos de data / hora decorridos / incrementais) e tem suporte adicional para fusos horários personalizados. Há “uma comparação detalhada aqui .
Novamente, https://github.com/zmwangx/ets . Experimente, relate bugs, etc.
Resposta
Coloque isso próximo ao início do seu script
# prepend a timestamp to all output exec &> >( ts "%Y-%m-%d.%H%M.%.S " )
Ou, para obter crédito extra, a saída para o arquivo de registro via:
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} )
para enviar para o console e para o arquivo de log:
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} )
As principais vantagens de fazer isso são separar o registro de todo o resto, não bagunçando o corpo do script canalizando para tee
ou semelhante em cada comando, e não tendo que escrever funções de registro personalizadas e aderindo ao antigo echo
.
O programa ts
é encontrado no pacote moreutils
, que deve estar prontamente disponível em qualquer regime de administração são. 🙂