睡眠を含むこの「while」ループのカウントダウンは機能しません。

睡眠を含むこの「while」ループのカウントダウンは機能しません。

私のスクリプトにはいくつかの問題がありますが、bash理由がわかりません。スクリプトは入力(分単位)を秒に変換してからゼロに達するまでカウントダウンを開始し、この時点でスクリプトが停止するように設計されています。

sleep 1途中で1つを追加すると、カウントダウンが停止します。なぜこれが起こるのですか?

パスワード:

if [ -z $1 ];
then
     read NUMBER
else NUMBER=$1
fi
SECONDS=$(( NUMBER * 60 ))

while [ $SECONDS -gt 0 ]
do
      sleep 1
      SECONDS=$(( SECONDS - 1))
      echo $SECONDS remaining
done

echo end

答え1

SECONDSスクリプトの縮小操作を妨げるように動作するBashで内部的に使用される変数。別の変数名(たとえば、SECSまたは)を使用するとseconds正しく機能します。たとえば、

#!/usr/bin/env bash

if [ -z $1 ];
then
     read NUMBER
else NUMBER=$1
fi

SECS=$(( NUMBER * 60 ))

while [ $SECS -gt 0 ]
do
      sleep 1
      SECS=$(( SECS - 1 ))
      echo $SECS remaining
done

echo end

Bashのマニュアルページでは、変数を使用したときに表示される動作について説明しますSECONDS

SECONDS
       Each time this parameter is referenced, it expands to the number of sec-
       onds since shell invocation.  If a value is  assigned  to  SECONDS,  the
       value returned upon subsequent references is the number of seconds since
       the assignment plus the value assigned.  The number of seconds at  shell
       invocation  and  the  current time are always determined by querying the
       system clock.  If SECONDS is unset, it  loses  its  special  properties,
       even if it is subsequently reset.

への各参照はSECONDS最後の割り当てから1秒なので、返される値は2番目に割り当てられた最初の値を加えた値です(ループ前while)。したがって、実際にループを通過するたびに減少がキャンセルされます。

1))(また、以前の使用法と一貫性を維持するために、および間にスペースを追加しました。)

答え2

〜のように@SottoVoceが言った。$SECONDSbash、Kornなどの他のシェルの特殊変数です。

bashとksh(zshではない)でwithなどの変数を設定解除すると、unset -v SECONDS特別な意味はなくなりますが、通常は環境スクリプトを除いて、シェルスクリプトではすべて大文字の変数名を使用したくありません。

スクリプトにはいくつかの他の問題があります。

#!/usr/bin/env bash

if [ -z $1 ];

これは言葉ではありません。周りの引用符を忘れてしまった$1ので、分割+globとnull削除の影響を受けます。したがって、$1isがnullまたは設定されていない場合は[ -z ]空でない文字列であることを確認するのと同じであるため、trueを返します。または、この場合、いくつかのエラーが発生します。一部のパラメータが渡されたことを確認するには、パラメータ数()を0と比較します。[ -n -z ]-z$1foo bar*$#

if [ "$#" -eq 0 ] # standard
if (( $# == 0 )) # Korn-like shells
then
     read NUMBER

readいくつかの文字の削除といくつかのバックスラッシュ処理があります。$IFSこれは数字には適していますが、通常は入力行を読む場合は次のようになります。

IFS= read -r minutes
else NUMBER=$1
fi

SECONDS=$(( NUMBER * 60 ))

算術式で削除されていないデータを使用することは、bashや他のKorn様シェルのコマンド注入の脆弱性です。

あなたは次のようなものが欲しい:

case $minutes in
  ("" | *[!0123456789]*) echo>&2 invalid number of minutes; exit 1;;
esac
(( seconds = minutes * 60 ))

番号の順序を確認してください。

while [ $SECONDS -gt 0 ]

bashは算術式でゼロで始まる数字を8進数として扱いますが、[数値比較式ではそうではありません。常に10進数で処理するので、混ぜて使用しないでください。

while (( seconds > 0 ))
do
      sleep 1
      SECONDS=$(( SECONDS - 1))
      echo $SECONDS remaining
done

次のようにすることもできます。

for (( seconds = minutes * 60; seconds > 0; seconds-- )); do
  echo "$seconds seconds remaining"
  sleep 1
done

ただし、コマンドの実行にも少し時間がかかることに注意してください。sleep 1プロセスを分岐してその内で実行するのに少なくとも1マイクロ秒かかり、ループ内でより多くのコマンドを実行する場合は時間がかかるため、1000回実行すると少なくとも1001秒sleepかかります。

この問題を解決するには、実際に特殊変数を使用できますが、$SECONDS現在使用している bash 内では使用できません。壊れたそして、浮動小数点にする方法はありません。 zshから:

#! /bin/zsh -
(( $# )) || vared -cp 'Enter the number of minutes: ' 1
[[ $1 = <-> ]] || {
  print -u2 Invalid number.
  exit 1
}

(( seconds = 60 * $1 ))
typeset -F SECONDS=0
for (( tick = 1; tick <= seconds; tick++ )); do
  printf '%g seconds remaining\n' seconds-SECONDS
  (( (delay = tick - SECONDS) <= 0 )) || sleep $delay
done

関連情報