Чому я не можу вбити тайм-аут, викликаний із сценарію 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 Спасибі, я пояснив приклади.
meetar

Відповіді:


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

Дуже підлий - прекрасно працював! Зараз я набагато розумніший, мерсі!
meetar

13

Спираючись на чудову відповідь, надану Жилем. Команда тайм-аута має параметр переднього плану, якщо використовується, тоді CTRL + C вийде з команди очікування.

#!/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-сигнали


Це правда, принаймні, до "рідко працює так, як очікувалося" (що залежить від ваших очікувань - ви повинні прочитати про групи процесів), але абсолютно не має значення. Питання не зраджує плутанини щодо 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.