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


61

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

今、私はtime -f '%e' ...(またはcommand time -f '%e' ...Bashビルトインのために)使用していますが、実行されたコマンドの出力をすでにリダイレクトしているため、timeコマンドの出力をキャプチャする方法について本当に失われています。基本的にはここでの問題は、ある出力を分離するのをtime実行したコマンド(複数可)の出力から。

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


編集:以下の2つの有用な答えを考えて、2つの説明を追加したかった。

  1. 実行されたコマンドの出力を破棄したくありませんが、それがstdoutまたはstderrで終わるかどうかは実際には関係ありません。
  2. 間接的な方法よりも直接的な方法をお勧めします(つまり、出力を中間ファイルに保存するのではなく、直接キャッチします)。

dateこれまで使用していたソリューションは、私が望むものに近くなります。


データを取得してそれを通常の実行中に処理する最も直接的な方法はfork()execvp()とを使用してCプログラムで実行することwait3()/wait4()です。これが最終的には時間と友人がやっていることです。私は、ファイルまたは同様のアプローチにリダイレクトせずにbash / perlでそれを行う簡単な方法を知りません。
ペンギン359

関連する質問がありますが、ここで興味深いことがあります
カレブ

@カレブ:ありがとう。本当に興味深い。ただし、私の目的では、スクリプトでそれで十分です。
-0xC0000022L

回答:


85

の出力をtimevarに取得するには、次を使用します。

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

4
ただし、コマンドの出力を破棄したくありませんでした。3番目のコードブロックは、私が考えていたものに最も近いと思います。最後のものをとして記述DIFF=$((END-START))しますが、算術式を使用します。:) ...答えてくれてありがとう。+1
0xC0000022L

1
@STATUS_ACCESS_DENIED:Bashは浮動小数点演算を行いません。したがって、2番目の解像度(.%Nbinfalseのコード)よりも優れている場合は、bc計算が必要であるか、より手間がかかります。
ジル 'SO-悪である停止

@Gilles:わかっています。私の質問で書いたように、整数は問題ありません。秒よりも高い解像度を持つ必要はありません。ただし、日付の呼び出しを変更する必要があります。しかし、私はそれを実現していました。
-0xC0000022L

6
参考までに、日付フォーマット%NはMac OS Xでは機能しないようで、単に「N」を返します。UbuntuでOK。
デビッド

time (cmd) 2> somethingはタイミングの出力をにリダイレクトする際に機能しますがfile、(ドキュメントによる)ことを意図したものではなく、他のシェルでtimeはキーワードがあり、バグと見なされる可能性があります。将来のバージョンでは動作しない可能性があるため、これには依存しませんbash
ステファンシャゼル

17

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

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

これによりout、fd 1、errfd 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:/')

errfd 2と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て、予測可能な形式の事実を利用できます(POSIX形式またはbash固有の変数time -pを取得するために使用する場合)。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は、前後に実行するのが簡単な解決策です(外部コマンドのロードに余分な時間がかかるためにわずかに不正確な場合)。


うわー、テストすることがたくさん。広範な返信をありがとう。+1。
-0xC0000022L

7

時間とともに、コマンド出力は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


1
ああ、わかりませんでした。timestderrへの書き込みは知っていましたが、stdoutと実行されたコマンドのstderrをstdoutにマージすることに気づきませんでした。しかし、一般的に直接的な方法はありません(つまり、中間ファイルなし)?+1。
0xC0000022L

3
@STATUS_ACCESS_DENIED:timeコマンドstdoutとをマージしませんstderr。私が示した方法では、コマンドの標準出力のみが必要であると仮定しました。からbash出てくるもののみを保存するため、stdoutリダイレクトする必要があります。コマンドのstderrを安全に削除できる場合:時刻は常にstderrの最後の行になります。コマンドの出力ストリームの両方が必要な場合は、別のスクリプトでラップすることをお勧めします。
マリヌス

6

あなたが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.

いいね 私は前にSECONDSのことを聞いたことがありません
Bruno9779

どのBashバージョンが導入されたのかを知っていますか?
0xC0000022L

4

そのためには、シェルとシェルから実行されるプロセスの累積ユーザー時間とシステム時間を出力するtimes(よりもtime)を使用するほうがおそらくよいでしょうbash

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

参照:help -m times詳細については。


4

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

1

インストール/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"

/usr/bin/time多くのシステムで
バジル・スタリンケビッチ

よく見ると、私の質問でそれを指摘しました。timeはBash(およびおそらく他のシェル)に組み込まれているシェルですが、time実行可能ファイルへのパスがである限り、組み込みコマンドではなく外部コマンドを実行するようにPATH使用できますcommand time
0xC0000022L

1

上記のステートメントを操作するとき、特にGrzegorzの答えを念頭に置いて、頭を上げてください。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
timeまた、組み込みシェルです。
バジルスタリンケビッチ

コマンドを実行してcommandいることを確認する場合は、を使用しますbuiltin。組み込みコマンドを実行していることを確認する場合は、を使用します。ここには魔法はありません。使用help可能なコマンドを特定type <command>したり、タイプ<command>(組み込み、外部コマンド、関数、エイリアスなど)を特定するために使用します。
0xC0000022L

バジル、あなたはまったく正しい。私はcommandコマンドの重要性を発見していませんでした。これは、実際に/ usr / bin / timeが呼び出されることを保証します。:これは、次の2つのステートメントは、互換性があることを意味$ command time -f %e sleep 4し、$ /usr/bin/time -f %e sleep
ポール・プリチャード

0

これを試してください。引数を指定して簡単なコマンドを実行し、$ 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

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

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

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.