web-dev-qa-db-ja.com

プログラムの実行時間を測定して変数内に保存する方法

Bash(v4 +)スクリプト内の特定の操作にかかる時間を調べるために、timeコマンドからの出力を「個別に」解析し、(最終的に)Bash変数(let VARNAME=...)。

今、私はtime -f '%e' ...(またはBashビルトインのためにcommand time -f '%e' ...)を使用していますが、実行したコマンドの出力をすでにリダイレクトしているので、どうすればいいのか迷っています。 timeコマンドの出力をキャプチャします。基本的にここでの問題は出力を分離するため of timeと実行されたコマンドの出力からです。

私が欲しいのは、コマンドの開始から完了までの時間を秒単位(整数)でカウントする機能です。 timeコマンドまたはそれぞれの組み込みコマンドである必要はありません。


編集:以下の2つの有用な回答を踏まえて、2つの説明を追加したいと思います。

  1. 実行したコマンドの出力を破棄したくありませんが、最終的にstdoutとstderrのどちらで終了するかは問題ではありません。
  2. 私は、間接的なアプローチよりも直接的なアプローチを好むでしょう(つまり、中間ファイルに保存するのではなく、出力を直接キャッチします)。

これまでのところdateを使用した解決策は、私が望むものに近づいています。

65
0xC0000022L

timeの出力をvarに取得するには、以下を使用します。

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

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

単一の時間タイプを要求することもできます。 utime:

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

時間を取得するには、date +%s.%Nを使用することもできるため、実行の前後にそれを使用して、差分を計算します。

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

Bashでは、time構成の出力は標準エラーに送られ、影響を受けるパイプラインの標準エラーをリダイレクトできます。それでは、出力とエラーストリームに書き込むコマンドから始めましょう:sh -c 'echo out; echo 1>&2 err'。コマンドのエラーストリームをtimeからの出力と混同しないように、コマンドのエラーストリームを一時的に別のファイル記述子に転送できます。

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

これは、outをfd 1に、errをfd 3に、時間を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:/')

Fd 2にerrを、fd 3に時間を指定すると、2つのファイル記述子を直接交換する方法がないので面倒です。

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

これは、コマンドの出力を後処理する方法を示していますが、コマンドの出力とその時間の両方をキャプチャする場合は、さらに努力する必要があります。一時ファイルの使用は1つの解決策です。実際、コマンドの標準エラーとその標準出力の両方をキャプチャする必要がある場合は、これが唯一の信頼できるソリューションです。しかし、それ以外の場合は、出力全体をキャプチャして、timeが予測可能な形式であることを利用できます(time -pPOSIX形式 またはbash固有のTIMEFORMAT変数)を取得します。

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

ウォールクロック時間だけに関心がある場合は、前後にdateを実行するのが簡単な解決策です(外部コマンドのロードに余分な時間がかかっているために少し不正確な場合)。

bashにいて(shではなく)、1秒未満の精度が必要ない場合は、dateへの呼び出しを完全にスキップして、余分なスポーンなしで実行できます。結合された出力を分離する必要がなく、コマンドからの出力をキャプチャして解析する必要がないプロセス:

# 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

時間とともに、コマンド出力はstdoutに出力され、時刻はstderrに出力されます。したがって、それらを分離するには、次のようにします。

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

しかし、今はファイルになっています。私はBashがstderrを変数に直接入れることができないと思います。コマンドの出力をどこかにリダイレクトしてもかまわない場合は、次のようにできます。

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

これを行うと、コマンドの出力はoutputfileになり、実行にかかった時間は$FOOになります。

7
marinus

OSXの場合、以前の応答をすべてまとめる

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

あなたはそうすることができます

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

そのためには、timestimebashより)を使用する方がおそらく良いでしょう。これは、シェルおよびシェルから実行されたプロセスの累積ユーザーおよびシステム時間を出力します。例使用する:

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

見る: help -m times詳細については。

4
kenorb

/bin/timeをインストールします(例:pacman -S time

したがって、-fフラグを試行したときのエラーの代わりに:

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

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

あなたは実際にそれを使うことができます:

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

そして、必要なものを取得します-変数の時間(例では、実際の経過時間に%eを使用し、他のオプションの場合はman timeを確認します):

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

echo "$variable"

上記のステートメントを処理するとき、特にグジェゴルツの答えを念頭に置いて頭を上げるのと同じように。私のUbuntu 16.04 Xenialシステムで、次の2つの結果に驚いた。

$ 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

エイリアスを設定していないので、なぜこれが起こっているのかわかりません。

1
Paul Pritchard

これを試してください。引数を指定して単純なコマンドを実行し、時間$ real $ user $ sysを設定して、終了コードを保持します。

また、実際のユーザーsys以外の変数でサブシェルやトランプルをフォークせず、スクリプトの実行を妨害しません。

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

例えば.

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

注:コンポーネントがサブシェルで実行されるパイプラインではなく、単純なコマンドのみを実行します

このバージョンでは、3回受け取る変数の名前を$ 1として指定できます。

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
}

例えば.

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

そして、時間を踏みにじることを避けるために、それが再帰的に呼び出される場合に役立ちます。しかし、その場合、RUなどはローカルで宣言して使用する必要があります。

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

0
Sam Liddicott