키 입력으로 Bash 스크립트에서 호출 된 시간 초과를 제거 할 수없는 이유는 무엇입니까? 때 ( 여기서 설명 ), Bash 스크립트

[편집 : 이것은 생성 된 모든 프로세스를 종료하는 방법을 묻는 다른 질문과 비슷합니다. 그 대답은 모두 pkill을 사용하는 것 같습니다. 제 질문의 핵심은 다음과 같습니다. Ctrl-C / Z를 스크립트에 의해 생성 된 모든 프로세스에 전파 할 수있는 방법이 있습니까?]

coreutils rectimeout명령 으로 SoX 를 호출 할 때 ( 여기서 설명 ), Bash 스크립트 내에서 호출 된 후 키 스트로크로 죽일 방법이없는 것 같습니다.

예 :

timeout 10 rec test.wav

… bash에서 Ctrl+ C또는 Ctrl+ 로 죽일 수 Z있지만 스크립트 내부에서 호출 된 경우에는 불가능합니다.

timeout 10 ping nowhere

…으로 죽일 수 Ctrl+ CCtrl+ 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+ CSIGINT신호를 보내고 Ctrl+ Z는 SIGTSTP신호를 보냅니다 .

SIGTSTP프로세스를 중지하고 SIGCONT계속 진행합니다.

이것은 명령 행에서 분기 된 포 그라운드 프로세스에서 작동합니다.

프로세스가 백그라운드 프로세스 인 경우 해당 신호를 다른 방식으로 프로세스에 보내야합니다. kill그렇게 할 것입니다. 이론적으로 그 살해에 대한 “-“연산자는 자식 프로세스를 나타내야하지만 이것은 예상대로 작동하지 않습니다.

추가 정보 : Unix-Signals


답변

기존 스크립트에 “찾기 및 바꾸기”접근 방식을 제공하여 @Gilles의 답변 개선 :

  1. 코드 시작 부분에이 스 니펫을 추가하십시오.
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
}
  1. 다음 코드를 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
  1. 스크립트에서 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 


답변