time
コマンドを2つの別々のコマンドで構成し、1つの出力を別のパイプに出力したいと思います。たとえば、次の2つのスクリプトを考えてみます。
$ cat foo.sh
#!/bin/sh
sleep 4
$ cat bar.sh
#!/bin/sh
sleep 2
time
でfoo.sh | bar.sh
にかかった時間を報告するにはどうすればよいですか(そして、はい、ここではパイプが意味をなさないことを知っていますが、これは単なる例です)?パイプせずにサブシェルで順次実行すると、期待どおりに機能します。
$ time ( foo.sh; bar.sh )
real 0m6.020s
user 0m0.010s
sys 0m0.003s
しかし、パイプするときにそれを機能させることができません:
$ time ( foo.sh | bar.sh )
real 0m4.009s
user 0m0.007s
sys 0m0.003s
$ time ( { foo.sh | bar.sh; } )
real 0m4.008s
user 0m0.007s
sys 0m0.000s
$ time sh -c "foo.sh | bar.sh "
real 0m4.006s
user 0m0.000s
sys 0m0.000s
私は同様の質問( 複数のコマンドで時間を実行し、時間出力をファイルに書き込む方法 )を読み、スタンドアロンのtime
実行可能ファイルも試しました:
$ /usr/bin/time -p sh -c "foo.sh | bar.sh"
real 4.01
user 0.00
sys 0.00
パイプのみを実行する3つ目のスクリプトを作成しても機能しません。
$ cat baz.sh
#!/bin/sh
foo.sh | bar.sh
そして、その時間:
$ time baz.sh
real 0m4.009s
user 0m0.003s
sys 0m0.000s
興味深いことに、最初のコマンドが実行された直後にtime
が終了するようには見えません。 bar.sh
を次のように変更した場合:
#!/bin/sh
sleep 2
seq 1 5
そして、再びtime
、time
出力がseq
の前に出力されることを期待していましたが、そうではありません:
$ time ( { foo.sh | bar.sh; } )
1
2
3
4
5
real 0m4.005s
user 0m0.003s
sys 0m0.000s
time
は、レポートの印刷前に終了するのを待っているにもかかわらず、実行にかかった時間bar.sh
をカウントしないようです1。
すべてのテストはArchシステムで実行され、bash 4.4.12(1)-releaseを使用しました。 zsh
または他の強力なシェルがそれを回避できる場合でも、これはその一部であるプロジェクトにのみbashを使用できますが、これは私にとって実行可能な解決策ではありません。
それで、パイプされたコマンドのセットの実行にかかった時間をどのように取得できますか?そして、私たちがそこにいる間、なぜそれはうまくいかないのですか? time
は、最初のコマンドが終了するとすぐに終了するように見えます。どうして?
私は次のようなもので個々の時間を取得できることを知っています:
( time foo.sh ) 2>foo.time | ( time bar.sh ) 2> bar.time
しかし、私はそれでもすべてを単一の操作として計時することが可能であるかどうか知りたいです。
1これはバッファの問題ではないようです。unbuffered
とstdbuf -i0 -o0 -e0
を使用してスクリプトを実行してみましたが、time
出力の前に数字がまだ出力されていました。
それは機能しています。
パイプラインのさまざまな部分が同時に実行されます。パイプライン内のプロセスを同期/シリアライズする唯一のことはIOです。つまり、1つのプロセスがパイプライン内の次のプロセスに書き込み、次のプロセスが最初のプロセスが書き込んだものを読み取ります。それとは別に、それらは互いに独立して独立して実行しています。
パイプライン内のプロセス間で読み取りまたは書き込みが発生しないため、パイプラインの実行にかかる時間は、最長のsleep
呼び出しの時間です。
あなたも書いたかもしれません
time ( foo.sh & bar.sh &; wait )
Terdonが投稿しました チャットで少し変更されたスクリプトの例 :
#!/bin/sh
# This is "foo.sh"
echo 1; sleep 1
echo 2; sleep 1
echo 3; sleep 1
echo 4
そして
#!/bin/sh
# This is "bar.sh"
sleep 2
while read line; do
echo "LL $line"
done
sleep 1
クエリは、「なぜtime ( sh foo.sh | sh bar.sh )
は3 + 3 = 6秒ではなく4秒を返すのですか?」
各コマンドが実行されるおおよその時間を含め、何が起こっているかを確認するには、次のようにします(出力には注釈が含まれます)。
$ time ( env PS4='$SECONDS foo: ' sh -x foo.sh | PS4='$SECONDS bar: ' sh -x bar.sh )
0 bar: sleep 2
0 foo: echo 1 ; The output is buffered
0 foo: sleep 1
1 foo: echo 2 ; The output is buffered
1 foo: sleep 1
2 bar: read line ; "bar" wakes up and reads the two first echoes
2 bar: echo LL 1
LL 1
2 bar: read line
2 bar: echo LL 2
LL 2
2 bar: read line ; "bar" waits for more
2 foo: echo 3 ; "foo" wakes up from its second sleep
2 bar: echo LL 3
LL 3
2 bar: read line
2 foo: sleep 1
3 foo: echo 4 ; "foo" does the last echo and exits
3 bar: echo LL 4
LL 4
3 bar: read line ; "bar" fails to read more
3 bar: sleep 1 ; ... and goes to sleep for one second
real 0m4.14s
user 0m0.00s
sys 0m0.10s
つまり、foo.sh
のecho
への最初の2つの呼び出しの出力がバッファリングされるため、パイプラインは6秒ではなく4秒かかります。
これはより良い例でしょうか?
$ time Perl -e 'alarm(3); 1 while 1;' | Perl -e 'alarm(4); 1 while 1;'
Alarm clock
real 0m4.004s
user 0m6.992s
sys 0m0.004s
スクリプトは、3秒と4秒(それぞれ)の間ビジーループし、並列実行のために合計で4秒、CPU時間は7秒かかります。 (少なくともおよそ。)
またはこれ:
$ time ( sleep 2; echo) | ( read x; sleep 3 )
real 0m5.004s
user 0m0.000s
sys 0m0.000s
これらは並行して実行されないため、合計所要時間は5秒です。すべてがスリープに費やされているため、CPU時間は使用されません。
sysdig
がある場合はトレーサを挿入できます コードを変更して必要な書き込みを/dev/null
に追加できると想定して、任意のポイントで
echo '>::blah::' >/dev/null
foo.sh | bar.sh
echo '<::blah::' >/dev/null
(しかし、それはあなたの「単一操作」要件に失敗します)そしてそれから物事を記録します
$ Sudo sysdig -w blalog "span.tags contains blah"
そして、あなたはおそらく期間だけをエクスポートするためにsysdigノミを必要とするでしょう
description = "Exports sysdig span tag durations";
short_description = "Export span tag durations.";
category = "Tracers";
args = {}
function on_init()
ftags = chisel.request_field("span.tags")
flatency = chisel.request_field("span.duration")
chisel.set_filter("evt.type=tracer and evt.dir=<")
return true
end
function on_event()
local tags = evt.field(ftags)
local latency = evt.field(flatency)
if latency then
print(tostring(tags) .. "\t" .. tonumber(latency) / 1e9)
end
return true
end
sysdig/chisels
ディレクトリにファイルspantagduration.lua
として保存すると、次のように使用できます。
$ sysdig -r blalog -c spantagduration
...
または、csysdig
またはJSON出力をいじることができます。
楽しすぎて、これが古いスレッドであることを知っていますが、同じ状況に陥っていました。エイリアスは簡単な回避策であることがわかりました。少なくともこれはbashとfishで機能します。すべてのシェルについてはわかりません。
代わりに:time ( foo.sh | bar.sh )
試してください:
alias foobar="foo.sh | bar.sh" time foobar