내 스크립트에 몇 가지 문제가 있지만 bash
이유를 모르겠습니다. 스크립트는 입력(분 단위)을 초로 변환한 다음 0에 도달할 때까지 카운트다운을 시작하고 이 지점에서 스크립트가 중지되도록 설계되었습니다.
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
마지막 할당 후 1초이므로 반환되는 값은 두 번째에 할당된 첫 번째 값을 더한 값입니다(루프 전 while
). 따라서 실제로 루프를 통과할 때마다 감소가 취소됩니다.
1
))
(또한 이전 사용법과 일관성을 유지하기 위해 및 사이에 공백을 추가했습니다 .)
답변2
~처럼@SottoVoce가 말했습니다., $SECONDS
bash 및 Korn과 같은 다른 쉘의 특수 변수입니다.
bash 및 ksh(zsh 아님)에서 with와 같은 변수를 설정 해제하면 unset -v SECONDS
특별한 의미가 제거되지만 일반적으로 환경 스크립트를 제외하고 쉘 스크립트에서는 모두 대문자로 된 변수 이름을 사용하고 싶지 않습니다.
스크립트에 몇 가지 다른 문제가 있습니다.
#!/usr/bin/env bash if [ -z $1 ];
이것은 말이 되지 않습니다. 주위에 따옴표를 잊어버렸 $1
으므로 분할+glob 및 null 제거의 영향을 받습니다. 따라서 $1
is가 null이거나 설정되지 않은 경우 [ -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 유사 셸의 명령 주입 취약점입니다..
당신은 다음과 같은 것을 원합니다 :
case $minutes in
("" | *[!0123456789]*) echo>&2 invalid number of minutes; exit 1;;
esac
(( seconds = minutes * 60 ))
번호 순서를 확인하세요.
while [ $SECONDS -gt 0 ]
bash는 산술 표현식에서 0으로 시작하는 숫자를 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