Почему я не могу убить тайм-аут, вызванный из скрипта Bash, нажатием клавиши?


11

[Редактировать: это похоже на некоторые другие вопросы, спрашивающие, как убить все порожденные процессы - ответы, похоже, используют pkill. Таким образом, суть моего вопроса может заключаться в следующем: есть ли способ распространения Ctrl-C / Z на все процессы, созданные скриптом?]

При вызове SoX recс помощью timeoutкоманды из coreutils (обсуждаемой здесь ), кажется, нет никакого способа убить его нажатием клавиши, после того как он был вызван из скрипта Bash.

Примеры:

timeout 10 rec test.wav

... можно убить с помощью Ctrl+ Cили Ctrl+ Zиз bash, но не тогда, когда он вызывается из скрипта.

timeout 10 ping nowhere

... можно убить с помощью Ctrl+ Cили Ctrl+ Zиз bash, и с Ctrl+, Zкогда он запускается из скрипта.

Я могу найти идентификатор процесса и уничтожить его таким образом, но почему я не могу использовать стандартное нажатие клавиши разрыва? И есть ли способ структурировать мой сценарий так, чтобы я мог?


2
Ctrl + Z не убивает процессы, только приостанавливает их. Они будут продолжать работать , если вы даете bgиз fgкоманд. В любом случае, есть ли разница между вашими 1-м и 3-м примерами?
Тердон

У меня нет timeoutв моей системе, но уничтожение sleepработает независимо от того, набирается ли оно непосредственно в командной строке, происходит из источника, выполняется или явно передается через интерпретатор
Кевин

@terdon Спасибо, я прояснил примеры.
встреча

Ответы:


18

Сигнальные клавиши, такие как 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

Если вы нажмете Ctrl+ Cчерез 2 секунды, все 5 секунд будут ждать, а затем напечатайте сообщение «Прервано». Это связано с тем, что оболочка воздерживается от выполнения кода прерывания, когда задание переднего плана активно.

Чтобы исправить это, запустите задание в фоновом режиме. В обработчике сигналов вызовите killдля передачи сигнала группе timeoutпроцессов.

#!/bin/sh
trap 'kill -INT -$pid' INT
timeout 5 sleep 10 &
pid=$!
wait $pid

Очень подлый - работал красиво! Я намного умнее, Мерси!
встреча

13

Опираясь на отличный ответ, предоставленный Жилем. Команда timeout имеет опцию переднего плана, если она используется, CTRL + C завершит команду timeout.

#!/bin/sh
trap 'echo caught interrupt and exiting;exit' INT
date
timeout --foreground 5 sleep 10
date

Это отличный ответ - проще, чем принятый ответ, и он отлично сработал для меня, используя timeoutGNU coreutils.
RichVel

1

В основном Ctrl+ Cотправляет SIGINTсигнал, а Ctrl+ Z отправляет SIGTSTPсигнал.

SIGTSTPпросто останавливает процесс, а SIGCONTпродолжит его.

Это работает на переднем плане-процесс, который был разветвлен в командной строке.

Если ваш процесс является фоновым процессом, вам придется отправлять этот сигнал другим процессам. killсделаю это. В теории оператор «-» - при этом уничтожении также должен сигнализировать о дочерних процессах, но это редко работает так, как ожидалось.

Для дальнейшего чтения: Unix-Signals


Это верно, по крайней мере, до «редко работает так, как ожидалось» (что зависит от ваших ожиданий - вы должны прочитать о группах процессов), но совершенно не имеет значения. Вопрос не предает путаницы в отношении SIGINT и SIGTSTP.
Жиль "ТАК - перестать быть злым"

0

Улучшение ответа @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 
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.