我的脚本有一些问题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 内部使用的变量,该变量的工作方式会干扰脚本的递减操作。如果您使用不同的变量名称(例如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
都是在上一次赋值之后一秒,因此返回的值是该秒加上分配的第一个值(循环之前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 和空删除的影响,所以如果$1
is 为空或未设置,则与检查是否为非空字符串[ -z ]
相同,因此它将返回 true然后。如果是或,则会导致一些错误。要检查某些参数是否已传递,请将参数数量 ( ) 与 0 进行比较:[ -n -z ]
-z
$1
foo 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 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