这个带有睡眠的“while”循环倒计时不起作用

这个带有睡眠的“while”循环倒计时不起作用

我的脚本有一些问题bash,但我不知道为什么。该脚本旨在将输入(以分钟为单位)转换为秒,然后开始倒计时直到达到零,此时脚本应停止。

当我sleep 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 内部使用的变量,该变量的工作方式会干扰脚本的递减操作。如果您使用不同的变量名称(例如SECSseconds),那么它可以正常工作。例如:

#!/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都是在上一次赋值之后一秒,因此返回的值是该秒加上分配的第一个值(循环之前while)。因此,实际上,每次循环都会撤销减量。

1(另外,我在和之间添加了一个空格)),以与您之前的用法保持一致)

答案2

作为@SottoVoce 说,$SECONDS是 bash 和其他类似 Korn 的 shell 中的特殊变量。

在 bash 和 ksh(不是 zsh)中,像 with 一样取消设置变量unset -v SECONDS会删除其特殊含义,但一般来说,除了环境脚本之外,您不希望在 shell 脚本中使用全大写的变量名称。

您的脚本中还有一些其他问题:

#!/usr/bin/env bash

if [ -z $1 ];

这没有道理。你忘记了 周围的引号$1,所以它受到 split+glob 和空删除的影响,所以如果$1is 为空或未设置,则与检查是否为非空字符串[ -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 的 shell 中的命令注入漏洞

你想要这样的东西:

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

确保您获得数字序列。

while [ $SECONDS -gt 0 ]

bash 将算术表达式中以 0 开头的数字视为八进制,但在[数字比较表达式中则不然,它们始终被视为十进制,因此不要混合和匹配。

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 11000 次可能至少需要 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

相关内容