web-dev-qa-db-fra.com

Comment mesurer le temps d'exécution du programme et le stocker dans une variable

Afin de savoir combien de temps certaines opérations dans un script Bash (v4 +) prennent, je voudrais analyser la sortie de la commande time "séparément" et (finalement) la capturer dans une variable Bash (let VARNAME=...).

Maintenant, j'utilise time -f '%e' ... (ou plutôt command time -f '%e' ... en raison de la fonction Bash intégrée), mais comme je redirige déjà la sortie de la commande exécutée, je suis vraiment perdu quant à la façon dont j'allais capturer la sortie de la commande time. Fondamentalement, le problème ici est pour séparer la sortie de time de la sortie des commandes exécutées.

Ce que je veux, c'est la fonctionnalité de compter le temps en secondes (entiers) entre le démarrage d'une commande et son achèvement. Il n'est pas nécessaire que ce soit la commande time ou la fonction intégrée correspondante.


Edit: compte tenu des deux réponses utiles ci-dessous, je voulais ajouter deux clarifications.

  1. Je ne veux pas jeter la sortie de la commande exécutée, mais peu importe qu'elle se retrouve sur stdout ou stderr.
  2. Je préférerais une approche directe à une approche indirecte (c'est-à-dire capturer la sortie directement au lieu de la stocker dans des fichiers intermédiaires).

La solution utilisant date jusqu'à présent se rapproche de ce que je veux.

65
0xC0000022L

Pour obtenir la sortie de time dans une var, utilisez ce qui suit:

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

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

Vous pouvez également demander un seul type d'heure, par exemple utime:

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

Pour obtenir l'heure, vous pouvez également utiliser date +%s.%N, prenez-le donc avant et après l'exécution et calculez le diff:

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

En bash, la sortie de la construction time va à son erreur standard, et vous pouvez rediriger l'erreur standard du pipeline qu'elle affecte. Commençons donc par une commande qui écrit dans ses flux de sortie et d'erreur: sh -c 'echo out; echo 1>&2 err'. Afin de ne pas confondre le flux d'erreur de la commande avec la sortie de time, nous pouvons temporairement détourner le flux d'erreur de la commande vers un descripteur de fichier différent:

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

Cela écrit out dans fd 1, err dans fd 3 et les heures dans 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:/')

Il serait plus agréable d'avoir err sur fd 2 et les heures sur fd 3, donc nous les échangeons, ce qui est lourd car il n'y a pas de moyen direct d'échanger deux descripteurs de fichiers:

{ { { 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:/')

Cela montre comment vous pouvez post-traiter la sortie de la commande, mais si vous souhaitez capturer à la fois la sortie de la commande et son heure, vous devez travailler plus fort. L'utilisation d'un fichier temporaire est une solution. En fait, c'est la seule solution fiable si vous devez capturer à la fois l'erreur standard de la commande et sa sortie standard. Mais sinon, vous pouvez capturer la sortie entière et profiter du fait que time a un format prévisible (si vous utilisez time -p pour obtenir format POSIX ou la variable TIMEFORMAT spécifique à 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*}

Si vous ne vous souciez que de l'heure de l'horloge murale, exécuter date avant et après est une solution simple (si légèrement plus imprécise en raison du temps supplémentaire consacré au chargement de la commande externe).

Si vous êtes dans bash (et non sh) et que vous n'avez PAS besoin d'une précision inférieure à une seconde, vous pouvez ignorer l'appel à date entièrement et le faire sans générer de supplément processus, sans avoir à séparer la sortie combinée, et sans avoir à capturer et analyser la sortie des commandes:

# 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.
8
sanpath

Avec le temps, la sortie de la commande sort sur stdout et l'heure sort sur stderr. Donc, pour les séparer, vous pouvez faire:

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

Mais, maintenant, le temps est dans un fichier. Je ne pense pas que Bash soit capable de mettre directement stderr dans une variable. Si cela ne vous dérange pas de rediriger la sortie de la commande quelque part, vous pouvez faire:

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

Lorsque vous effectuez cette opération, la sortie de la commande sera dans outputfile et le temps nécessaire à l'exécution sera dans $FOO.

7
marinus

En regroupant toutes les réponses précédentes, lorsque vous êtes sous OSX

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

tu peux faire comme

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"
}
4
loretoparisi

À cet effet, il est probablement préférable d'utiliser times (que time) dans bash qui affiche les temps utilisateur et système cumulés pour le shell et pour les processus exécutés à partir du shell, par exemple utilisation:

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

Voir: help -m times pour plus d'informations.

4
kenorb

Installer /bin/time (par exemple. pacman -S time)

Donc, au lieu d'une erreur lors de la tentative de -f drapeau:

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

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

Vous pouvez réellement l'utiliser:

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

Et obtenez ce que vous voulez - temps en variable (l'exemple utilise %e pour le temps réel écoulé, pour d'autres options, vérifiez man time):

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

echo "$variable"
1

Tout comme un avertissement lorsque vous travaillez avec les déclarations ci-dessus et surtout en tenant compte de la réponse de Grzegorz. J'ai été surpris de voir sur mon système Ubuntu 16.04 Xenial ces deux résultats:

$ 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

Je n'ai pas d'alias définis et je ne sais donc pas pourquoi cela se produit.

1
Paul Pritchard

essayez ceci, il exécute une commande simple avec des arguments et met les temps $ real $ user $ sys et préserve le code de sortie.

Il ne fourche pas non plus de sous-shell ou ne piétine aucune variable à l'exception du système utilisateur réel, et n'interfère pas autrement avec l'exécution du 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
}

par exemple.

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

note: il ne multiplie que la commande simple et non un pipeline, dont les composants sont exécutés dans un sous-shell

Cette version vous permet de spécifier comme $ 1 le nom des variables qui devraient recevoir les 3 fois:

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
}

par exemple.

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

et peut être utile s'il finit par être appelé récursivement, pour éviter de piétiner les heures; mais alors r u s etc devrait être déclaré local dans leur utilisation.

http://blog.sam.liddicott.com/2016/01/timeing-bash-commands.html

0
Sam Liddicott