사용자가 실수로 ctrl-c를 눌렀을 경우 "종료하시겠습니까?(y/n)"라는 메시지가 표시되도록 SIGINT
( )을 처리하려고 합니다 . CtrlCyes를 입력하면 스크립트를 종료합니다. 그렇지 않다면 인터럽트가 발생한 곳부터 계속하십시오. 기본적으로 ( )와 비슷하지만 조금 다른 방식으로 CtrlC작업 해야 합니다. 이를 달성하기 위해 다양한 방법을 시도했지만 예상한 결과를 얻지 못했습니다. 다음은 내가 시도한 몇 가지 시나리오입니다.SIGTSTP
CtrlZ
사례: 1
스크립트:play.sh
#!/bin/sh
function stop()
{
while true; do
read -rep $'\nDo you wish to stop playing?(y/n)' yn
case $yn in
[Yy]* ) echo "Thanks for playing !!!"; exit 1;;
[Nn]* ) break;;
* ) echo "Please answer (y/n)";;
esac
done
}
trap 'stop' SIGINT
echo "going to sleep"
for i in {1..100}
do
echo "$i"
sleep 3
done
echo "end of sleep"
위 스크립트를 실행하면 예상한 결과를 얻습니다.
산출:
$ play.sh
going to sleep
1
^C
Do you wish to stop playing?(y/n)y
Thanks for playing !!!
$ play.sh
going to sleep
1
2
^C
Do you wish to stop playing?(y/n)n
3
4
^C
Do you wish to stop playing?(y/n)y
Thanks for playing !!!
$
케이스: 2
for
루프를 새 스크립트로 이동하여 상위 프로세스와 하위 프로세스 가 loop.sh
됩니다 .play.sh
loop.sh
스크립트:play.sh
#!/bin/sh
function stop()
{
while true; do
read -rep $'\nDo you wish to stop playing?(y/n)' yn
case $yn in
[Yy]* ) echo "Thanks for playing !!!"; exit 1;;
[Nn]* ) break;;
* ) echo "Please answer (y/n)";;
esac
done
}
trap 'stop' SIGINT
loop.sh
스크립트:loop.sh
#!/bin/sh
echo "going to sleep"
for i in {1..100}
do
echo "$i"
sleep 3
done
echo "end of sleep"
이 경우 출력은 예상한 것과 다릅니다.
산출:
$ play.sh
going to sleep
1
2
^C
Do you wish to stop playing?(y/n)y
Thanks for playing !!!
$ play.sh
going to sleep
1
2
3
4
^C
Do you wish to stop playing?(y/n)n
$
프로세스가 신호를 받으면 SIGINT
신호를 모든 하위 프로세스에 전파하므로 두 번째 사례가 실패한다는 것을 이해합니다. SIGINT
하위 프로세스로 전파되는 것을 방지하여 loop.sh
첫 번째 경우에 작동했던 방식과 동일하게 작업을 수행할 수 있는 방법이 있습니까 ?
메모: 이것은 실제 적용 사례일 뿐입니다. 제가 작업 중인 애플리케이션에는 play.sh
및 에 여러 하위 스크립트가 있습니다 loop.sh
. 을 수신할 때 애플리케이션이 SIGINT
종료되지 않고 사용자에게 메시지를 표시해야 하는지 확인해야 합니다 .
답변1
좋은 예를 들어 작업 및 신호 관리에 대한 훌륭한 고전적인 질문입니다! 나는 신호 처리 메커니즘에 초점을 맞추기 위해 간단한 테스트 스크립트를 개발했습니다.
이를 달성하려면 백그라운드에서 하위 항목(loop.sh)을 시작한 후 wait
INT 신호를 수신하면 kill
PGID가 PID와 동일한 프로세스 그룹을 호출하십시오.
문제의 스크립트의 경우
play.sh
다음을 수행하여 수행할 수 있습니다.함수 에서 다음으로
stop()
대체하십시오 .exit 1
kill -TERM -$$ # note the dash, negative PID, kills the process group
loop.sh
백그라운드 프로세스로 시작 (여기에서 여러 백그라운드 프로세스를 시작하고 에서 관리할 수 있음play.sh
)loop.sh &
wait
모든 하위 항목을 기다리려면 스크립트 끝에 추가하세요 .wait
$$
스크립트가 프로세스를 시작하면 해당 하위는 상위 셸에 있는 상위 프로세스의 PID와 동일한 PGID를 가진 프로세스 그룹의 구성원이 됩니다 .
예를 들어, 스크립트는 백그라운드에서 trap.sh
세 개의 프로세스를 시작했고 현재 프로세스를 진행 중입니다. 프로세스 그룹 ID 열(PGID)이 상위 프로세스의 PID와 동일하다는 점에 유의하세요.sleep
wait
PID PGID STAT COMMAND
17121 17121 T sh trap.sh
17122 17121 T sleep 600
17123 17121 T sleep 600
17124 17121 T sleep 600
kill
Unix와 Linux에서는 음수 값으로 호출하여 해당 프로세스 그룹의 모든 프로세스에 신호를 보낼 수 있습니다 PGID
. 음수를 지정하면 kill
-PGID로 사용됩니다. 스크립트의 PID( $$
)가 PGID와 동일하므로 다음을 kill
사용하여 셸에서 프로세스 그룹을 만들 수 있습니다.
kill -TERM -$$ # note the dash before $$
신호 번호나 이름을 제공해야 합니다. 그렇지 않으면 kill의 일부 구현에서 "잘못된 옵션" 또는 "잘못된 신호 사양"이라고 알려줄 것입니다.
아래의 간단한 코드는 이 모든 것을 보여줍니다. 신호 핸들러를 설정하고 trap
3개의 자식을 생성한 다음 끝없는 대기 루프에 들어가 kill
신호 핸들러의 프로세스 그룹 명령에 의해 스스로 종료되기를 기다립니다.
$ cat trap.sh
#!/bin/sh
signal_handler() {
echo
read -p 'Interrupt: ignore? (y/n) [Y] >' answer
case $answer in
[nN])
kill -TERM -$$ # negative PID, kill process group
;;
esac
}
trap signal_handler INT
for i in 1 2 3
do
sleep 600 &
done
wait # don't exit until process group is killed or all children die
샘플 실행은 다음과 같습니다.
$ ps -o pid,pgid,stat,args
PID PGID STAT COMMAND
8073 8073 Ss /bin/bash
17111 17111 R+ ps -o pid,pgid,stat,args
$
확인, 추가 프로세스가 실행되지 않습니다. 테스트 스크립트를 시작하고, 중단하고( ^C
), 중단을 무시하도록 선택한 다음 일시 중지합니다( ^Z
).
$ sh trap.sh
^C
Interrupt: ignore? (y/n) [Y] >y
^Z
[1]+ Stopped sh trap.sh
$
실행 중인 프로세스를 확인하고 프로세스 그룹 번호( PGID
)를 기록해 두십시오.
$ ps -o pid,pgid,stat,args
PID PGID STAT COMMAND
8073 8073 Ss /bin/bash
17121 17121 T sh trap.sh
17122 17121 T sleep 600
17123 17121 T sleep 600
17124 17121 T sleep 600
17143 17143 R+ ps -o pid,pgid,stat,args
$
테스트 스크립트를 포그라운드( fg
)로 가져오고 다시 인터럽트( ^C
)합니다. 이번에는 다음을 선택합니다.~ 아니다무시하다:
$ fg
sh trap.sh
^C
Interrupt: ignore? (y/n) [Y] >n
Terminated
$
실행 중인 프로세스를 확인하세요. 더 이상 잠들지 마세요.
$ ps -o pid,pgid,stat,args
PID PGID STAT COMMAND
8073 8073 Ss /bin/bash
17159 17159 R+ ps -o pid,pgid,stat,args
$
셸에 대한 참고 사항:
내 시스템에서 실행되도록 코드를 수정해야 했습니다. #!/bin/sh
스크립트의 첫 번째 줄이 있지만 스크립트는 /bin/sh에서 사용할 수 없는 확장명(bash 또는 zsh의)을 사용합니다.
답변2
"프로세스가 SIGINT 신호를 받으면 해당 신호를 모든 하위 프로세스에 전파한다는 것을 이해합니다."
어디서 이런 잘못된 생각을 얻었나요?
$ perl -E '$SIG{INT}=sub { say "ouch $$" }; if (fork()) { say "parent $$"; sleep 3; kill 2, $$ } else { say "child $$"; sleep 99 }'
parent 25831
child 25832
ouch 25831
$
주장된 대로 전파가 있었다면 하위 프로세스에서 "아야"를 예상했을 수도 있습니다.
실제로:
"터미널의 인터럽트 키(종종 DELETE 또는 Control-C) 또는 종료 키(종종 Control-백슬래시)를 입력할 때마다 인터럽트 신호 또는 종료 신호가 전경 프로세스 그룹의 모든 프로세스로 전송됩니다." -- W .리차드 스티븐스. "UNIX® 환경의 고급 프로그래밍". 애디슨-웨슬리. 1993. p.246.
이는 다음을 통해 관찰할 수 있습니다.
$ perl -E '$SIG{INT}=sub { say "ouch $$" }; fork(); sleep 99'
^Couch 25971
ouch 25972
$
SIGINT
포그라운드 프로세스 그룹의 모든 프로세스 는 에서 a를 먹기 때문에 Control+C해당 신호를 적절하게 처리하거나 무시하거나 하위 프로세스가 새로운 포그라운드 프로세스 그룹이 되도록 모든 프로세스를 설계해야 합니다. 예를 들어 쉘)은 더 이상 포그라운드 프로세스 그룹에 속하지 않기 때문에 신호를 볼 수 없습니다.
답변3
소스 에서 play.sh
루프 파일은 다음과 같습니다.
source ./loop.sh
트랩으로 인해 한 번 종료된 서브쉘은 트랩 종료 시 다시 돌아갈 수 없습니다.
답변4
문제는 sleep
.
때를CTRL+C
당신은 a를 죽인다 sleep
- 당신이 아니라sh
(귀하의 구문이 #!/bin/sh
약간 의심스럽더라도). 그러나 전체 프로세스 그룹은 신호를 받습니다. 왜냐하면 loop.sh
서브셸인 핸들러를 설치하지 않았기 때문에 신호는 즉시 종료됩니다.
에 트랩이 필요합니다 loop.sh
. 명시적으로 지정하지 않는 한 시작되는 모든 서브쉘에 대해 트랩이 지워집니다.trap ''
SIG
부모로부터 무시당함.
-m
또한 자녀를 추적할 수 있도록 부모의 작업 제어 모니터링이 필요합니다 . wait
예를 들어, 작업 제어에서만 작동합니다. -m
감시자 모드는 쉘이 터미널과 상호 작용하는 방식입니다.
sh -cm ' . /dev/fd/3' 3<<"" # `-m`onitor is important
sh -c ' kill -INT "$$"'& # get the sig#
wait %%;INT=$? # keep it in $INT
trap " stty $(stty -g;stty -icanon)" EXIT # lose canonical input
loop()( trap exit INT # this is a child
while [ "$#" -le 100 ] # same as yours
do echo "$#" # writing the iterator
set "" "$@" # iterating
sleep 3 # sleeping
done
)
int(){ case $1 in # handler fn
($INT) printf "\33[2K\rDo you want to stop playing? "
case $(dd bs=1 count=1; echo >&3) in
([Nn]) return
esac; esac; exit "$1"
} 3>&2 >&2 2>/dev/null
while loop ||
int "$?"
do :; done
0
1
2
3
4
Do you want to stop playing? n
0
1
2
3
Do you want to stop playing? N
0
1
Do you want to stop playing? y
[mikeserv@desktop tmp]$