Керуйте, який процес скасовується Ctrl + C


13

У мене є живий компакт-диск, який завантажується в Linux і запускає невеликий сценарій Bash. Сценарій шукає та запускає другу програму (яка зазвичай є компільованою двійковою C ++).

Ви повинні мати можливість перервати другу програму, натиснувши Ctrl+ C. Що має статися, це те, що друга програма зупиняється, а сценарій Bash продовжує виконувати очищення. Що на насправді відбувається те , що і основне додаток і Bash скрипт завершується. Яка проблема.

Тому я використовував trapвбудований, щоб сказати Bash ігнорувати SIGINT. І тепер Ctrl+ Cприпиняє додаток C ++, але Bash продовжує свою роботу. Чудово.

О так ... Іноді "другий додаток" - це ще один сценарій Баша. І в цьому випадку Ctrl+ Cзараз нічого не робить .

Ясно, що моє розуміння того, як працює цей матеріал, неправильне ... Як я можу контролювати, який процес отримує SIGINT, коли користувач натискає Ctrl+ C? Я хочу направити цей сигнал лише на один конкретний процес .

Відповіді:


11

Після багатьох, багато годин пошуку обличчя в Інтернеті я знайшов відповідь.

  1. У Linux є поняття групи процесів .

  2. Драйвер TTY має поняття групи переднього плану.

  3. При натисканні кнопки Ctrl+ C, то TTY посилає SIGINTдо кожного процесу в Foreground Process Group. (Дивіться також цю запис у блозі .)

Ось чому і компільований двійковий файл, і сценарій, який його запускає, отримують клобування. Насправді я хочу лише, щоб головна програма отримувала цей сигнал, а не сценарії запуску.

Рішення зараз очевидно: нам потрібно помістити додаток у нову групу процесів і зробити це передплановою групою процесів для цієї TTY. Мабуть, команда зробити це є

setsid -c <applcation>

І це все. Тепер, коли користувач натисне Ctrl+ C, SIGINT буде надісланий додатку (і будь-яким дітям, які він може мати), і нікому більше. Якого я хотів.

  • setsid сам по собі ставить додаток у нову групу процесів (дійсно, цілий новий «сеанс», який, мабуть, є групою груп процесів).

  • Додавання -cпрапора змушує цю нову групу процесів стати групою переднього плану для поточної TTY. (Тобто, це стає, SIGINTколи ви натискаєте Ctrl+ C)

Я бачив багато суперечливої ​​інформації про те, коли Bash робить чи не запускає процеси в новій групі процесів. (Зокрема, це виглядає по-різному для "інтерактивних" та "неінтерактивних" снарядів.) Я бачив пропозиції, що ви можете, можливо, змусити це працювати з розумними хитрощами труб ... Я не знаю. Але підхід вище, здається, працює для мене.


5
Ви майже отримали це ... Управління роботою вимкнено за замовчуванням під час запуску сценарію, але ви можете його включити set -m. Це трохи чистіше і простіше, ніж використовувати setsidкожен раз, коли ви запускаєте дитину.
psusi

@psusi Дякую за пораду! Мені потрібно бігати лише однією дитиною, так що нічого страшного. Тепер я знаю, куди шукати в посібнику Баша ...
MathematicalOrchid

За іронією долі, у мене є зворотна проблема, коли я хочу, щоб батько ловив підпис, але це не через "хитру хитрування труб". Також група прогресу ->
процесна

2

Як я вже згадував у коментарі до f01, вам слід надсилати SIGTERM до дочірнього процесу. Ось пара сценаріїв, які показують, як захопити ^ C та відправити сигнал дочірнього процесу.

По-перше, батько.

траптест

#!/bin/bash

# trap test
# Written by PM 2Ring 2014.10.23

myname=$(basename "$0")
child=sleeploop

set_trap()
{
    sig=$1
    msg="echo -e \"\n$myname received ^C, sending $sig to $child, $pid\""
    trap "$msg; kill -s $sig $pid" SIGINT
}
trap "echo \"bye from $myname\"" EXIT

echo "running $child..."
./$child 5  &
pid=$!

# set_trap SIGINT
set_trap SIGTERM
echo "$child pid = $pid"

wait $pid
echo "$myname finished waiting"

А тепер, дитина.

сонник

#!/bin/bash

# child script for traptest
# Written by PM 2Ring 2014.10.23

myname=$(basename "$0")
delay="$1"

set_trap()
{
    sig=$1
    trap "echo -e '\n$myname received $sig signal';exit 0" $sig
}

trap "echo \"bye from $myname\"" EXIT
set_trap SIGTERM
set_trap SIGINT

#Select sleep mode
if false
then
    echo "Using foreground sleep"
    Sleep()
    {
        sleep $delay
    }
else
    echo "Using background sleep"
    Sleep()
    {
        sleep "$delay" &
        wait $!
    }
fi

#Time to snooze :)
for ((i=0; i<5; i++));
do
    echo "$i: sleeping for $delay"
    Sleep
done

echo "$myname terminated normally"

Якщо traptest надсилає SIGTERM, то поводиться добре, але якщо traptest надсилає SIGINT, сплячий цикл ніколи цього не бачить.

Якщо сплячий цикл захоплює SIGTERM, а режим сну виходить на перший план, то він не може реагувати на сигнал, поки не прокинеться з поточного сну. Але якщо режим сну є фоновим, він відповість негайно.


Дякую за цей чудовий приклад, він дуже допоміг мені зрозуміти, як я можу вдосконалити свій сценарій :)
TabeaKischka

2

У своєму початковому скрипті bash.

  • слідкувати за PID другої програми

  • зловити ЗІГІНТ

  • коли ви зловили SIGINT, надішліть SIGINT до другої програми PID


1
Це може не допомогти. Якщо ви потрапляєте на SIGINT у батьківському скрипті, то дочірні сценарії не зможуть отримувати SIGINT, незалежно від того, надіслали ви його від батьківського або іншого оболонки. Але це не так вже й погано, як це звучить, адже ви можете надіслати SIGTERM дитині, що, очевидно, є кращим сигналом для використання.
PM 2Ring

1
Чому SIGTERM "кращий"?
Математична

Вони майже схожі. SIGINT - сигнал, що надсилається керуючим терміналом / користувачем, наприклад, Ctrl + C. SIGTERM - це те, що ви також можете надіслати, якщо ви хочете, щоб процес припинився. Більше думок тут en.wikipedia.org/wiki/Unix_signal
f01
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.