web-dev-qa-db-ja.com

bashスクリプト:修正されたスリープ時間はwhileループで加算されます

私はbashスクリプトに不慣れで、いくつかのサンプルスクリプトから始めました。

1つは次のとおりです。

#!/bin/bash
SECONDS=5
i=1

while true
do
        echo "`date`: Loop $i"
        i=$(( $i+1 ))
        sleep $SECONDS
done

これは次の結果になります:

Sunday 10 May  15:08:20 AEST 2020: Loop 1
Sunday 10 May  15:08:25 AEST 2020: Loop 2
Sunday 10 May  15:08:35 AEST 2020: Loop 3
Sunday 10 May  15:08:55 AEST 2020: Loop 4

...そして、私がスクリプトに期待していたことや望んでいたことではありません。

ループを実行するたびに、秒が2倍になるのはなぜですか

bash --version
GNU bash, version 4.3.48(1)-release (x86_64-pc-linux-gnu)
Copyright (C) 2013 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
13
MaxG

SECONDSはbashの内部変数だからです。シェルが実行されていた時間を格納します。

man bash


このパラメーターが参照されるたびに、シェルの呼び出しからの秒数が返されます。値がSECONDSに割り当てられている場合、以降の参照で返される値は、割り当てからの秒数と割り当てられた値です。

したがって、スクリプトは次のように機能します。

  1. 最初の使用では、5の値を入力として受け入れ、初期値は5です。
  2. 次に5秒のスリープがあり、そのスリープが戻ると、SECONDSの値は初期値の5秒+スリープの5秒= 10秒になります。
  3. 次に、10秒のスリープ(現在のSECONDSの値)に加えて、以前の累積時間10秒に20を加えます。
  4. 20秒間スリープを繰り返します。前の値の20は40です。
  5. 等.
26
Isaac

この構成には2次の問題があります。ループ内の処理にかなりの時間がかかる場合(たとえば1.4秒)、ループは6.4秒ごとにしか繰り返されません。

それが重要な場合は、代わりにループをカウントできます。

#!/bin/bash

Expired=$(( SECONDS + 23 ))
Tick=5
Iter=1

while (( SECONDS < Expired )); do
    echo "$(date '+%T.%N'): Loop $Iter"
    sleep 1.4  #.. Simulate work.
    echo "$(date '+%T.%N'): Idle"
    sleep $(( Iter++ * Tick - SECONDS ))
done                    

ループ時間は、SECONDSの整数精度内で安定しています。

Expiredを初期化するために、より複雑な計算を実行できます(たとえば、2つの日付コマンドを使用して、真夜中の前の秒を検索します)。

5
Paul_Pedant