[편집 : 이것은 생성 된 모든 프로세스를 종료하는 방법을 묻는 다른 질문과 비슷합니다. 그 대답은 모두 pkill을 사용하는 것 같습니다. 제 질문의 핵심은 다음과 같습니다. Ctrl-C / Z를 스크립트에 의해 생성 된 모든 프로세스에 전파 할 수있는 방법이 있습니까?]
coreutils rec
의 timeout
명령 으로 SoX 를 호출 할 때 ( 여기서 설명 ), Bash 스크립트 내에서 호출 된 후 키 스트로크로 죽일 방법이없는 것 같습니다.
예 :
timeout 10 rec test.wav
… bash에서 Ctrl+ C또는 Ctrl+ 로 죽일 수 Z있지만 스크립트 내부에서 호출 된 경우에는 불가능합니다.
timeout 10 ping nowhere
…으로 죽일 수 Ctrl+ C나 Ctrl+ Z배쉬에서, 그리고와 Ctrl+ Z가 스크립트 내부에서 실행할 때.
프로세스 ID를 찾아서 죽일 수는 있지만 표준 중단 키 입력을 사용할 수없는 이유는 무엇입니까? 그리고 내가 할 수 있도록 스크립트를 구성하는 방법이 있습니까?
답변
Ctrl+ 와 같은 신호 키 C는 포 그라운드 프로세스 그룹 의 모든 프로세스에 신호를 보냅니다 .
일반적인 경우 프로세스 그룹은 파이프 라인입니다. 예를 들어에서에서 head <somefile | sort
실행중인 프로세스 head
와 실행중인 sort
프로세스는 셸과 동일한 프로세스 그룹에 있으므로 모두 신호를 수신합니다. 백그라운드 ( somecommand &
) 에서 작업을 실행할 때 해당 작업은 자체 프로세스 그룹에 있으므로 Ctrl+를 눌러도 C영향을 미치지 않습니다.
timeout
프로그램은 자체 프로세스 그룹에 자신을 배치합니다. 소스 코드에서 :
/* Ensure we're in our own group so all subprocesses can be killed.
Note we don't just put the child in a separate group as
then we would need to worry about foreground and background groups
and propagating signals between them. */
setpgid (0, 0);
시간 초과가 발생하면 해당 timeout
구성원이 속한 프로세스 그룹을 종료 하는 간단한 편의를 수행합니다. 자체 프로세스를 별도의 프로세스 그룹에 넣었으므로 상위 프로세스는 그룹에 없습니다. 여기에서 프로세스 그룹을 사용하면 하위 애플리케이션이 여러 프로세스로 분기되는 경우 모든 프로세스가 신호를 수신하게됩니다.
timeout
명령 행 에서 직접 실행 하고 Ctrl+ C를 누르면 결과 SIGINT가 timeout
하위 프로세스에 의해 수신 되지만 timeout
상위 프로세스 인 대화식 쉘에서는 수신 되지 않습니다 . 때 timeout
스크립트에서 호출되는 스크립트를 실행에만 쉘은 신호를 수신 : timeout
그것은 다른 프로세스 그룹이기 때문에 그것을 얻을하지 않습니다.
trap
내장 스크립트를 사용하여 쉘 스크립트에서 신호 핸들러를 설정할 수 있습니다 . 불행히도 그렇게 간단하지 않습니다. 이걸 고려하세요:
#!/bin/sh
trap 'echo Interrupted at $(date)' INT
date
timeout 5 sleep 10
date
2 초 후에 Ctrl+ 를 누르면 C여전히 5 초 동안 대기 한 다음 “Interrupted”메시지를 인쇄합니다. 포 그라운드 작업이 활성화되어있는 동안 쉘이 트랩 코드를 실행하지 못하게하기 때문입니다.
이를 해결하려면 백그라운드에서 작업을 실행하십시오. 신호 처리기에서 호출 kill
하여 신호를 timeout
프로세스 그룹 에 릴레이합니다 .
#!/bin/sh
trap 'kill -INT -$pid' INT
timeout 5 sleep 10 &
pid=$!
wait $pid
답변
Gilles가 제공 한 탁월한 답변을 기반으로합니다. 시간 종료 명령에는 포 그라운드 옵션이 있으며, 사용하는 경우 CTRL + C는 시간 종료 명령을 종료합니다.
#!/bin/sh
trap 'echo caught interrupt and exiting;exit' INT
date
timeout --foreground 5 sleep 10
date
답변
기본적으로 Ctrl+ C는 SIGINT
신호를 보내고 Ctrl+ Z는 SIGTSTP
신호를 보냅니다 .
SIGTSTP
프로세스를 중지하고 SIGCONT
계속 진행합니다.
이것은 명령 행에서 분기 된 포 그라운드 프로세스에서 작동합니다.
프로세스가 백그라운드 프로세스 인 경우 해당 신호를 다른 방식으로 프로세스에 보내야합니다. kill
그렇게 할 것입니다. 이론적으로 그 살해에 대한 “-“연산자는 자식 프로세스를 나타내야하지만 이것은 예상대로 작동하지 않습니다.
추가 정보 : Unix-Signals
답변
기존 스크립트에 “찾기 및 바꾸기”접근 방식을 제공하여 @Gilles의 답변 개선 :
- 코드 시작 부분에이 스 니펫을 추가하십시오.
declare -a timeout_pids
exec 21>&1; exec 22>&2 # backup file descriptors, see /superuser//a/1446738/187576
my_timeout(){
local args tp ret
args="$@"
timeout $args &
tp=$!
echo "pid of timeout: $tp"
timeout_pids+=($tp)
wait $tp
ret=$?
count=${#timeout_pids[@]}
for ((i = 0; i < count; i++)); do
if [ "${timeout_pids[i]}" = "$tp" ] ; then
unset 'timeout_pids[i]'
fi
done
return $ret
}
- 다음 코드를
INT
핸들러에 추가 (또는 병합)하십시오 .
pre_cleanup(){
exec 1>&21; exec 2>&22 # restore file descriptors, see /superuser//a/1446738/187576
echo "Executing pre-cleanup..."
for i in "${timeout_pids[*]}"; do
if [[ ! -z $i ]]; then
#echo "Killing PID: $i"
kill -INT -$i 2> /dev/null
fi
done
exit
}
trap pre_cleanup INT
- 스크립트에서
timeout
명령을my_timeout
기능으로 대체하십시오 .
예
다음은 예제 스크립트입니다.
#!/bin/bash
# see "killing timeout": /unix//a/57692/65781
declare -a timeout_pids
exec 21>&1; exec 22>&2 # backup file descriptors, see /superuser//a/1446738/187576
my_timeout(){
local args tp ret
args="$@"
timeout $args &
tp=$!
echo "pid of timeout: $tp"
timeout_pids+=($tp)
wait $tp
ret=$?
count=${#timeout_pids[@]}
for ((i = 0; i < count; i++)); do
if [ "${timeout_pids[i]}" = "$tp" ] ; then
unset 'timeout_pids[i]'
fi
done
return $ret
}
cleanup(){
echo "-----------------------------------------"
echo "Restoring previous routing table settings"
}
pre_cleanup(){
exec 1>&21; exec 2>&22 # restore file descriptors, see /superuser//a/1446738/187576
echo "Executing pre-cleanup..."
for i in "${timeout_pids[*]}"; do
if [[ ! -z $i ]]; then
echo "Killing PID: $i"
kill -INT -$i 2> /dev/null
fi
done
exit
}
trap pre_cleanup INT
trap cleanup EXIT
echo "Executing 5 temporary timeouts..."
unreachable_ip="192.168.44.5"
my_timeout 1s ping -c 1 "$unreachable_ip" &> /dev/null
my_timeout 1s ping -c 1 "$unreachable_ip" &> /dev/null
my_timeout 1s ping -c 1 "$unreachable_ip" &> /dev/null
my_timeout 1s ping -c 1 "$unreachable_ip" &> /dev/null
my_timeout 1s ping -c 1 "$unreachable_ip" &> /dev/null
echo "ctrl+c now to execute cleanup"
my_timeout 9s ping -c 1 "$unreachable_ip" &> /dev/null