Prefácio de cada linha com quanto tempo demorou para gerá-lo

3

Eu gostaria de ter um script que precisasse de cada linha de um stdin com informações quanto tempo levou para gerá-lo. Basicamente para entrada:

foo
bar
baz

Eu gostaria de ter

0 foo
10 bar
5 baz

Quando 10 são 10 segundos passados entre a impressão foo e a barra de impressão, semelhante para 5, demorou 5 segundos após a impressão da barra para imprimir baz.

Eu sei que existe um utilitário ts que mostra os timestamps e eu sei sobre link , mas eu preferiria não usar javascript para fazer isso.

Existe uma ferramenta padrão para isso ou devo usar o awk e fazer o processamento?

    
por Krzysztof Krasoń 08.09.2017 / 20:59

5 respostas

5

Vamos supor que o script que gera a saída seja chamado de generate . Em seguida, para exibir o número de segundos necessários para gerar cada linha:

$ generate | ( t0=$(date +%s); while read -r line; do t1=$(date +%s); echo " $((t1-t0)) $line"; t0=$t1; done )
 2 foo
 0 foo
 5 foo
 3 foo

Os mesmos comandos espalhados por várias linhas se parecem com:

generate | ( t0=$(date +%s)
    while read -r line
    do
        t1=$(date +%s)
        echo " $((t1-t0)) $line"
        t0=$t1
    done
    )

Como alternativa, por conveniência, podemos definir uma função de shell que contenha este código:

timer() { t0=$(date +%s); while read -r line; do t1=$(date +%s); echo " $((t1-t0)) $line"; t0=$t1; done; }

Podemos usar esta função da seguinte forma:

$ generate | timer
 0 foo
 2 foo
 4 foo
 3 foo

Como funciona

  • t0=$(date +%s)

    Isso captura a hora atual no início do script em segundos desde a época.

  • while read -r line; do

    Isso inicia um loop que lê a partir da entrada padrão

  • t1=$(date +%s)

    Isso captura o tempo em segundos desde a época em que a linha atual foi capturada.

  • echo " $((t1-t0)) $line"

    Isso imprime o tempo em segundos que levou para a linha atual.

  • t0=$t1

    Isso atualiza t0 para a próxima linha.

  • done

    Isso sinaliza o fim do loop while .

por 08.09.2017 / 21:06
5
trickle () {
    awk 'BEGIN { t = systime(); OFS="\t" }
               { print systime() - t, $0 }
               { t = systime()           }'
}

Esta pequena função de shell simplesmente envolve um script awk (compatível com o GNU awk e mawk -Wi , mas não com o BSD awk devido à falta de systime() ). Ele irá mostrar cada linha de entrada prefixada com o número de segundos inteiros desde a última linha de saída. A primeira linha será prefixada com um zero.

Teste:

$ { echo hello; sleep 2; echo world } | trickle
0       hello
2       world

O script awk poderia obviamente ser executado como autônomo:

$ some_command | awk 'BEGIN{OFS="\t";t=systime()}{print systime()-t,$0;t=systime()}

Por exemplo,

$ { echo hello; sleep 2; echo world; } | awk 'BEGIN{OFS="\t";t=systime()}{print systime()-t,$0;t=systime()}'
0       hello
2       world
    
por 08.09.2017 / 23:24
2

Bem, moreutils ts parece ser uma ferramenta perfeita para isso:

por segundos desde o início:

$ seq 5 | pv -qL 1 | ts -s %s
2 1
4 2
6 3
8 4
10 5

Ou com mais precisão:

$ seq 5 | pv -qL 1 | ts -s %.s
1.949228 1
3.933483 2
5.917923 3
7.902231 4
9.886618 5

Ou com zsh ou ksh93 ( $SECONDS é inteiro por padrão, mas você pode fazê-lo flutuar com typeset -F ; pdksh e bash tem $SECONDS também, mas apenas inteiro):

$ typeset -F SECONDS=0; seq 5 | pv -qL 1 | while IFS= read -r line; do
   printf '%.3f %s\n' "$SECONDS" "$line"; done
1.987 1
3.972 2
5.956 3
7.941 4
9.925 5

por segundos entre as linhas

$ seq 5 | pv -qL 1 | ts -i %.s
1.947814 1
1.984442 2
1.984456 3
1.984151 4
1.984195 5

zsh / ksh93 :

$ seq 5 | pv -qL 1 | while SECONDS=0;  IFS= read -r line; do
  printf '%.6f %s\n' "$SECONDS" "$line"; done
1.985298 1
1.984322 2
1.984212 3
1.984240 4
1.984441 5
    
por 09.09.2017 / 00:08
2

Você pode usar time (nota: um binário separado, não o shell embutido) e combiná-lo com um loop while. Para obter o tempo de execução de cada comando:

while read -r line; do
    /usr/bin/time -f "%e %C" $line 2>&1
done

Exemplo de saída:

0.1 sleep 0.1
0.2 sleep 0.2
1.0 sleep 1

Caso você queira o tempo total de execução desde o início, você precisa somar o tempo de execução para cada etapa. A precisão do formato %e é de 1/100 de segundo, portanto, você não pode usar a aritmética inteira do shell diretamente ( dependendo do shell que você está usando).

total=0.0

while read -r line; do
    t=$(/usr/bin/time -f "%e" $line 2>&1)
    total=$(awk "BEGIN{print $total+$t}")
    echo "$total $line"
done

Exemplo de saída:

0.1 sleep 0.1
0.3 sleep 0.2
1.3 sleep 1
    
por 08.09.2017 / 22:21
1

Primeiro, crie uma função simples aproveitando o bash builtin SECONDS . Observe que inicializamos a zero em cada chamada.

_time() { SECONDS=0; out="$($@)"; echo "[${SECONDS}] ${out:-no output}"; }

Em seguida, use esta função _time para executar o (s) comando (s) de uma lista de comandos que pode ser um arquivo deles, ou o que for.

while read cmd; do _time $cmd; done < <(echo -e "sleep 5\nsleep 2\nsleep 1")

Aqui, o sono não tem saída, mas seus comandos provavelmente serão. Isso é simplesmente para mostrar o uso de SECONDS no que se refere a esse problema. Este exemplo produzirá a saída padrão de no output :

[5] no output
[2] no output
[1] no output
    
por 09.09.2017 / 06:30