Como medir o tempo de execução do programa e armazenar isso dentro de uma variável

52

Para descobrir por quanto tempo determinadas operações dentro de um script Bash (v4 +), gostaria de analisar a saída do comando time "separadamente" e (finalmente) capturá-lo dentro de uma variável Bash ( let VARNAME=... ).

Agora, estou usando time -f '%e' ... (ou melhor, command time -f '%e' ... por causa do Bash interno), mas como já redirecionei a saída do comando executado, estou realmente perdida em relação a como eu iria capture a saída do comando time . Basicamente, o problema aqui é separar a saída de time da saída do (s) comando (s) executado (s).

O que eu quero é a funcionalidade de contar a quantidade de tempo em segundos (inteiros) entre o início de um comando e sua conclusão. Não precisa ser o comando time ou o respectivo built-in.

Editar: dadas as duas respostas úteis abaixo, eu queria adicionar dois esclarecimentos.

  1. Eu não quero jogar fora a saída do comando executado, mas não importa se ele acaba em stdout ou stderr.
  2. Eu preferiria uma abordagem direta sobre uma indireta (ou seja, capturar a saída diretamente em vez de armazená-la em arquivos intermediários).

A solução usando date até agora chega ao que eu quero.

    
por 0xC0000022L 26.04.2011 / 21:17

9 respostas

74

Para obter a saída de time em um var, use o seguinte:

usr@srv $ mytime="$(time ( ls ) 2>&1 1>/dev/null )"
usr@srv $ echo "$mytime"

real    0m0.006s
user    0m0.001s
sys     0m0.005s

Você também pode solicitar um único tipo de horário, por exemplo, utime:

usr@srv $ utime="$( TIMEFORMAT='%lU';time ( ls ) 2>&1 1>/dev/null )"
usr@srv $ echo "$utime"
0m0.000s

Para obter o tempo, você também pode usar date +%s.%N , então pegue-o antes e depois da execução e calcule o diff:

START=$(date +%s.%N)
command
END=$(date +%s.%N)
DIFF=$(echo "$END - $START" | bc)
# echo $DIFF

Espero que ajude.

    
por 26.04.2011 / 21:33
16

No bash, a saída da construção time vai para o erro padrão e você pode redirecionar o erro padrão do pipeline que ela afeta. Então, vamos começar com um comando que grava seus streamas de saída e erro: sh -c 'echo out; echo 1>&2 err' . Para não misturar o fluxo de erros do comando com a saída de time , podemos desviar temporariamente o fluxo de erros do comando para um descritor de arquivo diferente:

{ time -p sh -c 'echo out; echo 1>&2 err' 2>&3; }

Isto escreve out para fd 1, err para fd 3 e os tempos para fd 2:

{ time -p sh -c 'echo out; echo 1>&2 err' 2>&3; } \
    3> >(sed 's/^/ERR:/') 2> >(sed 's/^/TIME:/') > >(sed 's/^/OUT:/')

Seria mais agradável ter err no fd 2 e os tempos no fd 3, então nós os trocamos, o que é complicado porque não há nenhum modo direto de trocar dois descritores de arquivos:

{ { { time -p sh -c 'echo out; echo 1>&2 err' 2>&3; } 3>&2 2>&4; } 4>&3; } 3> >(sed 's/^/TIME:/') 2> >(sed 's/^/ERR:/') > >(sed 's/^/OUT:/')

Isso mostra como você pode pós-processar a saída do comando, mas se você quiser capturar a saída do comando e seus horários, precisará trabalhar mais. Usando um arquivo temporário é uma solução. Na verdade, é a única solução confiável se você precisar capturar o erro padrão do comando e sua saída padrão. Caso contrário, você pode capturar toda a saída e aproveitar o fato de que time tem um formato previsível (se você usa time -p para obter o formato POSIX ou a variável TIMEFORMAT específica do bash).

nl=$'\n'
output=$(TIMEFORMAT='%R %U %S %P'; mycommand)
set ${output##*$nl}; real_time=$1 user_time=$2 system_time=$3 cpu_percent=$4
output=${output%$nl*}

Se você se importa apenas com o relógio de parede, executar date antes e depois é uma solução simples (se for um pouco mais imprecisa devido ao tempo extra gasto carregando o comando externo).

    
por 26.04.2011 / 23:10
7

Com o tempo, a saída do comando sai no stdout e o tempo sai no stderr. Então, para separá-los, você pode fazer:

command time -f '%e' [command] 1>[command output file] 2>[time output file]

Mas agora a hora está em um arquivo. Eu não acho que Bash é capaz de colocar stderr em uma variável diretamente. Se você não se importa em redirecionar a saída do comando para algum lugar, você pode fazer:

FOO=$((( command time -f '%e' [command]; ) 1>outputfile; ) 2>&1; )

Quando você fizer isso, a saída do comando estará em outputfile e o tempo que levará para ser executado será em $FOO .

    
por 26.04.2011 / 21:43
4

Para isso, provavelmente é melhor usar times (do que time ) em bash , que imprime o tempo acumulado de usuário e sistema para o shell e para processos executados a partir do shell, exemplo use:

$ (sleep 2; times) | (read tuser tsys; echo $tuser:$tsys)
0m0.001s:0m0.003s

Veja: help -m times para mais informações.

    
por 17.01.2016 / 15:57
4

Em Colocar todas as respostas anteriores, quando estiver no OSX

ProductName:    Mac OS X
ProductVersion: 10.11.6
BuildVersion:   15G31+

você pode fazer como

microtime() {
    python -c 'import time; print time.time()'
}
compute() {
    local START=$(microtime)
    #$1 is command $2 are args
    local END=$(microtime)
    DIFF=$(echo "$END - $START" | bc)
    echo "$1\t$2\t$DIFF"
}
    
por 01.08.2016 / 15:46
4

Se você estiver em bash (e não em sh ) e NÃO precisar de precisão abaixo de um segundo, poderá ignorar a chamada para date totalmente e fazer isso sem gerar nenhum processo extra, sem precisar separar a saída combinada e sem ter que capturar e analisar a saída de qualquer comando:

# SECONDS is a bash special variable that returns the seconds since set.
SECONDS=0
mycmd <infile >outfile 2>errfile
DURATION_IN_SECONDS=$SECONDS
# Now '$DURATION_IN_SECONDS' is the number of seconds.
    
por 22.03.2018 / 21:41
1

Instale /bin/time (por exemplo, pacman -S time )

Então, em vez de erro ao tentar -f flag:

$ time -f %e sleep 0.5
bash: -f: command not found

real    0m0.001s
user    0m0.001s
sys     0m0.001s

Você pode realmente usá-lo:

$ /bin/time -f %e sleep 0.5
0.50

E obtenha o que você deseja - tempo na variável (o exemplo usa %e para o tempo real decorrido, para outras opções, verifique man time ):

#!/bin/bash
tmpf="$(mktemp)"
/bin/time -f %e -o "$tmpf" sleep 0.5
variable="$(cat "$tmpf")"
rm "$tmpf"

echo "$variable"
    
por 29.07.2017 / 14:04
1

Apenas como um líder quando se trabalha com as declarações acima e especialmente tendo em conta a resposta de Grzegorz. Fiquei surpreso ao ver no meu sistema Ubuntu 16.04 Xenial esses dois resultados:

$ which time
/usr/bin/time

$ time -f %e sleep 4
-f: command not found
real    0m0.071s
user    0m0.056s
sys     0m0.012s

$ /usr/bin/time -f %e sleep 4
4.00

Eu não tenho nenhum apelido definido e não sei por que isso está acontecendo.

    
por 19.09.2017 / 13:22
0

tente isso, ele executa um comando simples com argumentos e coloca os tempos $ real $ user $ sys e preserva o código de saída.

Ele também não bifurca subshells nem atropela quaisquer variáveis exceto o usuário real sys, e não interfere com a execução do script

timer () {
  { time { "$@" ; } 2>${_} {_}>&- ; } {_}>&2 2>"/tmp/$$.$BASHPID.${#FUNCNAME[@]}"
  set -- $?
  read -d "" _ real _ user _ sys _ < "/tmp/$$.$BASHPID.${#FUNCNAME[@]}"
  rm -f "/tmp/$$.$BASHPID.${#FUNCNAME[@]}"
  return $1
}

por exemplo.

  timer find /bin /sbin /usr rm /tmp/
  echo $real $user $sys

nota: apenas vezes o comando simples não é um pipeline, cujos componentes são executados em um subshell

Esta versão permite que você especifique como $ 1 o nome das variáveis que devem receber as 3 vezes:

timer () {
  { time { "${@:4}" ; } 2>${_} {_}>&- ; } {_}>&2 2>"/tmp/$$.$BASHPID.${#FUNCNAME[@]}"
  set -- $? "$@"
  read -d "" _ "$2" _ "$3" _ "$4" _ < "/tmp/$$.$BASHPID.${#FUNCNAME[@]}"
  rm -f "/tmp/$$.$BASHPID.${#FUNCNAME[@]}"
  return $1
}

por exemplo.

  timer r u s find /bin /sbin /usr rm /tmp/
  echo $r $u $s

e pode ser útil se acabar sendo chamado recursivamente, para evitar atropelamentos; mas então os rs etc. devem ser declarados locais em seu uso.

link

    
por 27.01.2016 / 11:12