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.
La solution utilisant date
jusqu'à présent se rapproche de ce que je veux.
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
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.
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
.
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"
}
À 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.
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"
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.
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