Wie man die Zeit der Programmausführung misst und diese in einer Variablen speichert

61

Um herauszufinden, wie lange bestimmte Operationen in einem Bash (v4 +) - Skript dauern, möchte ich die Ausgabe des timeBefehls "separat" analysieren und (letztendlich) in einer Bash - Variablen ( let VARNAME=...) erfassen .

Jetzt benutze ich time -f '%e' ...(oder eher command time -f '%e' ...wegen des eingebauten Bash), aber da ich die Ausgabe des ausgeführten Befehls bereits umleite, bin ich wirklich verloren, wie ich die Ausgabe des timeBefehls erfassen würde . Grundsätzlich besteht hier das Problem darin , die Ausgabe von timevon der Ausgabe der ausgeführten Befehle zu trennen .

Was ich möchte, ist die Funktionalität , die Zeitspanne in Sekunden (Ganzzahlen) zwischen dem Starten eines Befehls und seiner Fertigstellung zu zählen. Es muss nicht das timeKommando oder das jeweilige eingebaute sein.


Bearbeiten: Angesichts der beiden folgenden nützlichen Antworten wollte ich zwei Klarstellungen hinzufügen.

  1. Ich möchte die Ausgabe des ausgeführten Befehls nicht wegwerfen, aber es spielt keine Rolle, ob er auf stdout oder stderr endet.
  2. Ich würde einen direkten Ansatz einem indirekten vorziehen (dh die Ausgabe wird direkt abgefangen und nicht in Zwischendateien gespeichert).

Die datebisherige Lösung kommt dem nahe, was ich will.

0xC0000022L
quelle
Der direkteste Weg , um die Daten zu erhalten und damit umgehen , während sie noch in einem C - Programm zu tun , wäre es normal laufen zu lassen mit fork(), execvp()und wait3()/wait4(). Dies ist letztendlich, was Zeit und Freunde tun. Mir ist keine einfache Möglichkeit bekannt, dies in bash / perl zu tun, ohne zu einer Datei oder einem ähnlichen Ansatz umzuleiten.
Pinguin359
Es gibt eine verwandte Frage, die Sie hier vielleicht interessant finden .
Caleb
@Caleb: danke. In der Tat interessant. Für meine Zwecke ist es jedoch ausreichend, dies in einem Skript zu tun.
0xC0000022L

Antworten:

85

Um die Ausgabe von timein eine Variable zu bekommen, verwenden Sie folgendes:

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

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

Sie können auch nur nach einem Zeittyp fragen, z. B. utime:

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

Um die Zeit zu erhalten, die Sie auch verwenden können date +%s.%N, nehmen Sie sie vor und nach der Ausführung und berechnen Sie den Diff:

START=$(date +%s.%N)
command
END=$(date +%s.%N)
DIFF=$(echo "$END - $START" | bc)
# echo $DIFF
binfalse
quelle
4
Ich wollte die Ausgabe des Befehls jedoch nicht wegwerfen. Ich denke, Ihr dritter Codeblock kommt dem, was ich mir vorgestellt habe, am nächsten. Obwohl ich den letzten als schreiben würde, unter DIFF=$((END-START))Verwendung der arithmetischen Ausdrücke. :) ... Danke für die Antwort. +1
0xC0000022L
1
@STATUS_ACCESS_DENIED: Bash führt keine Fließkomma-Arithmetik durch. Wenn Sie also eine bessere Auflösung als die zweite Auflösung ( .%Nim Binfalse-Code) wünschen, müssen Sie entweder bcBerechnungen durchführen oder diese verfeinern.
Gilles 'SO- hör auf böse zu sein'
@ Gilles: Ich weiß. Wie ich in meiner Frage schrieb, sind ganze Zahlen in Ordnung. Eine höhere Auflösung als die Sekunde ist nicht erforderlich. Aber danke, der Datumsaufruf müsste geändert werden. Das hatte ich allerdings gemerkt.
0xC0000022L
6
Zu Ihrer Information, die Datumsformatierung% N scheint unter Mac OS X nicht zu funktionieren, sie gibt nur "N" zurück. Ok auf Ubuntu.
David
Beachten Sie, dass das time (cmd) 2> somethingUmleiten der Zeitsteuerungsausgabe auf zwar filefunktioniert, jedoch nicht (wie in der Dokumentation angegeben). In anderen Shells timeist dies kein Schlüsselwort und kann als Fehler angesehen werden . Ich würde mich nicht darauf verlassen, da es in zukünftigen Versionen von möglicherweise nicht funktioniert bash.
Stéphane Chazelas
17

In bash wird die Ausgabe des timeKonstrukts auf den Standardfehler zurückgesetzt, und Sie können den Standardfehler der betroffenen Pipeline umleiten. Lassen Sie uns also mit einem Befehl starten, der an seinem Ausgang und Fehler streamas schreibt: sh -c 'echo out; echo 1>&2 err'. Um den Fehlerstrom des Befehls nicht mit der Ausgabe von zu verwechseln time, können wir den Fehlerstrom des Befehls vorübergehend in einen anderen Dateideskriptor umleiten:

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

Dies schreibt outauf fd 1, errfd 3 und die Zeiten auf 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:/')

Es wäre angenehmer, errfd 2 und die Zeiten fd 3 zu haben, also tauschen wir sie aus, was umständlich ist, da es keine direkte Möglichkeit gibt, zwei Dateideskriptoren auszutauschen:

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

Dies zeigt, wie Sie die Ausgabe des Befehls nachbearbeiten können. Wenn Sie jedoch sowohl die Ausgabe des Befehls als auch dessen Zeiten erfassen möchten, müssen Sie härter arbeiten. Die Verwendung einer temporären Datei ist eine Lösung. Tatsächlich ist dies die einzige zuverlässige Lösung, wenn Sie sowohl den Standardfehler als auch die Standardausgabe des Befehls erfassen müssen. Ansonsten können Sie die gesamte Ausgabe erfassen und die Tatsache nutzen, dass timesie ein vorhersagbares Format hat (wenn Sie time -pdas POSIX-Format oder die bash-spezifische TIMEFORMATVariable verwenden).

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*}

Wenn Sie sich nur um die Uhrzeit der Wanduhr kümmern, ist das dateVorher- Nachher-Laufen eine einfache Lösung (wenn dies aufgrund der zusätzlichen Ladezeit des externen Befehls etwas ungenauer ist).

Gilles 'SO - hör auf böse zu sein'
quelle
Wow, viel zu testen. Vielen Dank für die ausführliche Antwort. +1.
0xC0000022L
7

Mit der Zeit wird die Befehlsausgabe auf stdout und die Zeit auf stderr ausgegeben. Um sie zu trennen, können Sie also Folgendes tun:

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

Aber jetzt ist die Zeit in einer Datei. Ich glaube nicht, dass Bash stderr direkt in eine Variable einfügen kann. Wenn es Ihnen nichts ausmacht, die Ausgabe des Befehls irgendwo umzuleiten, können Sie Folgendes tun:

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

Wenn Sie dies tun, wird die Ausgabe des Befehls in outputfileund die Ausführungszeit in sein $FOO.

Marinus
quelle
1
Oh, ich wusste es nicht. Ich wusste time, dass stdout und stderr des ausgeführten Befehls in stdout zusammengeführt werden. Aber generell gibt es keine direkte Methode (also ohne Zwischendatei)? +1.
0xC0000022L
3
@STATUS_ACCESS_DENIED: timeführt die Befehle stdoutund nicht zusammen stderr. Die Art und Weise, wie ich es zeigte, ging davon aus, dass Sie nur die Standardausgabe des Befehls benötigten. Da basherst gespeichert wird, was rauskommt, stdoutmuss man umleiten. Wenn Sie das stderr Ihres Befehls sicher löschen können: Die Uhrzeit steht immer in der letzten Zeile von stderr. Wenn Sie beide Ausgabestreams Ihres Befehls benötigen, würde ich vorschlagen, sie in ein anderes Skript zu packen.
marinus
6

Wenn Sie sich in bash(und nicht in sh) befinden und keine Genauigkeit von weniger als einer Sekunde benötigen, können Sie den Aufruf datevollständig überspringen und dies tun, ohne zusätzliche Prozesse auszulösen, ohne die kombinierte Ausgabe zu trennen und ohne die Ausgabe zu erfassen und zu analysieren aus beliebigen Befehlen:

# 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.
sanpath
quelle
Nett. Ich habe noch nie von SECONDS gehört
Bruno9779
Weißt du zufällig, ab welcher Bash-Version dies eingeführt wurde?
0xC0000022L
4

Zu diesem Zweck ist es wahrscheinlich besser, times(als time) zu verwenden, in bashdem die akkumulierten Benutzer- und Systemzeiten für die Shell und für Prozesse, die von der Shell ausgeführt werden, gedruckt werden. Beispiel:

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

Siehe: help -m timesfür mehr Infos.

Kenorb
quelle
4

Beim Zusammenfassen aller vorherigen Antworten unter OSX

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

Sie können gerne tun

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"
}
loretoparisi
quelle
1

Installieren /bin/time(zB pacman -S time)

Also statt Fehler beim Versuch -fflag:

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

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

Sie können es tatsächlich verwenden:

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

Und bekommen Sie, was Sie wollen - Zeit in Variable (Beispiel verwendet %efür die real verstrichene Zeit, für andere Optionen überprüfen man time):

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

echo "$variable"
Grzegorz Wierzowiecki
quelle
/usr/bin/timeauf vielen Systemen
Basile Starynkevitch
Wenn Sie genau hinsehen, habe ich das in meiner Frage erwähnt. timeist eine in Bash eingebaute Shell (und vermutlich auch andere Shells). Solange sich der Pfad zur timeausführbaren Datei in befindet PATH, können wir command timesicherstellen, dass wir den externen Befehl im Gegensatz zum eingebauten ausführen.
0xC0000022L
1

Gerade als Heads-Up bei der Arbeit mit den obigen Aussagen und vor allem unter Berücksichtigung von Grzegorz 'Antwort. Ich war überrascht, auf meinem Ubuntu 16.04 Xenial-System diese beiden Ergebnisse zu sehen:

$ 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

Ich habe keine Aliase festgelegt und weiß daher nicht, warum dies geschieht.

Paul Pritchard
quelle
1
timeist auch eine eingebaute Shell.
Basile Starynkevitch
Wenn Sie sicherstellen möchten, dass Sie den Befehl commandausführen, verwenden Sie, wenn Sie sicherstellen möchten, dass Sie den integrierten Befehl ausführen, verwenden Sie builtin. Hier gibt es keine Magie. Verwenden Sie helpdiese Option, um die verfügbaren Befehle oder type <command>den Typ <command>(z. B. einen eingebauten, externen Befehl, eine Funktion oder einen Alias) zu ermitteln.
0xC0000022L
Basile, du hast recht. Ich hatte die Bedeutung des commandBefehls nicht erkannt . Dadurch wird sichergestellt, dass / usr / bin / time aufgerufen wird. Dies bedeutet, dass die folgenden beiden Aussagen kompatibel sind: $ command time -f %e sleep 4und$ /usr/bin/time -f %e sleep
Paul Pritchard,
0

Wenn Sie dies versuchen, führt es einen einfachen Befehl mit Argumenten aus, setzt $ real $ user $ sys und behält den Exit-Code bei.

Es werden auch keine Subshells gespalten oder Variablen außer dem echten Benutzersys mit Füßen getreten und die Ausführung des Skripts wird nicht anderweitig gestört

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

z.B

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

hinweis: es ist nur mal der einfache befehl keine pipeline, deren komponenten in einer subshell ausgeführt werden

In dieser Version können Sie als $ 1 den Namen der Variablen angeben, die dreimal empfangen werden sollen:

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
}

z.B

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

und kann nützlich sein, wenn es am Ende rekursiv aufgerufen wird, um ein Überfahren der Zeiten zu vermeiden; Aber dann sollte rus etc in ihrer Verwendung als lokal deklariert werden.

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

Sam Liddicott
quelle