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

NeG*_*NeG 7 linux scripting bash

我的脚本有一些问题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
Run Code Online (Sandbox Code Playgroud)

Sot*_*oce 20

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
Run Code Online (Sandbox Code Playgroud)

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.
Run Code Online (Sandbox Code Playgroud)

由于 的每次引用SECONDS都是在上次赋值之后一秒,因此返回的值是该秒加上分配的第一个值(循环之前while)。因此,实际上,每次循环都会撤销减量。

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


Sté*_*las 10

正如@SottoVoce 所说$SECONDS是 bash 和其他类似 Korn 的 shell 中的一个特殊变量。

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

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

#!/usr/bin/env bash

if [ -z $1 ];
Run Code Online (Sandbox Code Playgroud)

这没有道理。你忘记了 周围的引号$1,所以它受到 split+glob 和空删除的影响,所以如果$1is 为空或未设置,则变得与检查是否为非空字符串[ -z ]相同,因此它将返回 true然后。如果是或,则会导致一些错误。要检查某些参数是否已传递,请将参数数量 ( ) 与 0 进行比较:[ -n -z ]-z$1foo bar*$#

if [ "$#" -eq 0 ] # standard
if (( $# == 0 )) # Korn-like shells
Run Code Online (Sandbox Code Playgroud)
then
     read NUMBER
Run Code Online (Sandbox Code Playgroud)

请注意,read会进行一些字符剥离$IFS和一些反斜杠处理,这对于数字来说可能就很好,但通常要读取一行输入,它是:

IFS= read -r minutes
Run Code Online (Sandbox Code Playgroud)
else NUMBER=$1
fi

SECONDS=$(( NUMBER * 60 ))
Run Code Online (Sandbox Code Playgroud)

在算术表达式中使用未经处理的数据是 bash 和其他类似 Korn 的 shell 中的命令注入漏洞

你想要这样的东西:

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

确保您获得数字序列。

while [ $SECONDS -gt 0 ]
Run Code Online (Sandbox Code Playgroud)

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

while (( seconds > 0 ))
Run Code Online (Sandbox Code Playgroud)
do
      sleep 1
      SECONDS=$(( SECONDS - 1))
      echo $SECONDS remaining
done
Run Code Online (Sandbox Code Playgroud)

你还可以这样做:

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

但请注意,运行命令也需要一些时间,运行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
Run Code Online (Sandbox Code Playgroud)