Neden bir Bash betiğinden çağrılan zaman aşımını bir tuşa basarak öldüremiyorum?


11

[Düzenle: Bu, tüm ortaya çıkan süreçlerin nasıl öldürüleceğini soran diğer bazı sorulara benzer - cevapların hepsi pkill kullanmak gibi görünüyor. Yani sorumun özü şu olabilir: Ctrl-C / Z'yi bir komut dosyası tarafından oluşturulan tüm işlemlere yaymanın bir yolu var mı?]

Coreutils ( burada ele recalınan) timeoutkomutuyla bir SoX çağırırken , bir Bash betiğinden çağrıldıktan sonra bir tuş vuruşu ile öldürmenin herhangi bir yolu yoktur.

Örnekler:

timeout 10 rec test.wav

... bash'tan Ctrl+ Cveya Ctrl+ ile öldürülebilir Z, fakat bir senaryo içinden çağrıldığında olamaz.

timeout 10 ping nowhere

... ile öldürülebilir Ctrl+ Cveya Ctrl+ Zbash ve ile Ctrl+ Zbir komut dosyası içinden yayınlandığında, bir.

İşlem kimliğini bulabilir ve bu şekilde öldürebilirim, ama neden standart bir mola tuş vuruşunu kullanamıyorum? Senaryomu yapılandırmamın bir yolu var mı?


2
Ctrl + Z işlemleri öldürmez, yalnızca duraklatır. Eğer verirsen onlar çalışmaya devam edecektir bgait fgkomutları. Her neyse, 1. ve 3d örnekleriniz arasında bir fark var mı?
terdon

Ben yok timeoutsistemimi ama öldürmeyi sleepaçıkça tercüman aracılığıyla, doğrudan komut satırına yazdığınız kaynaklı, idam veya geçirilen olsun eserlerini
Kevin

@terdon Teşekkürler, örnekleri açıkladım.
meetar

Yanıtlar:


19

Ctrl+ Gibi sinyal tuşları C, ön plan işlem grubundaki tüm işlemlere bir sinyal gönderir .

Tipik durumda, bir proses grubu bir boru hattıdır. Örneğin, içinde head <somefile | sort, çalışan işlem headve çalışan sortişlem kabukla aynı işlem grubundadır, böylece hepsi sinyali alır. Arka planda ( somecommand &) bir iş çalıştırdığınızda , o iş kendi işlem grubundadır, bu nedenle Ctrl+ tuşuna basmak işi Cetkilemez.

timeoutProgramın kendi süreç grubunda kendini yerleştirir. Kaynak koddan:

/* 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);

Bir zaman aşımı oluştuğunda, timeoutüyesi olduğu işlem grubunu öldürmenin basit bir yolunu bulur. Kendini ayrı bir süreç grubuna soktuğundan, üst süreci grupta olmayacaktır. Burada bir işlem grubu kullanmak, alt uygulama birkaç işleme ayrılırsa, tüm işlemlerinin sinyali almasını sağlar.

timeoutDoğrudan komut satırında çalıştırıp Ctrl+ tuşuna bastığınızda C, sonuçta elde edilen SIGINT hem timeoutalt işlem tarafından hem de alt işlem tarafından alınır, ancak timeoutüst işlem olan etkileşimli kabuk tarafından alınmaz . Bir timeoutkomut dosyasından çağrıldığında, yalnızca komut dosyasını çalıştıran kabuk sinyali alır: timeoutfarklı bir işlem grubunda olduğu için alamaz.

Yerleşik bir kabuk betiğinde bir sinyal işleyici ayarlayabilirsiniz trap. Ne yazık ki, o kadar basit değil. Bunu düşün:

#!/bin/sh
trap 'echo Interrupted at $(date)' INT
date
timeout 5 sleep 10
date

Ctrl+ CDüğmesine 2 saniye sonra basarsanız , bu işlem tam 5 saniyeyi bekler, ardından “Araya Girildi” mesajını yazdırır. Çünkü kabuk, bir ön plan işi etkinken tuzak kodunu çalıştırmaktan kaçınır.

Bunu düzeltmek için işi arka planda çalıştırın. Sinyal işleyicide, killsinyali timeoutproses grubuna aktarmak için arayın .

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

Çok sinsi - güzel çalıştı! Artık çok daha zekiyim merci!
meetar

14

Gilles tarafından verilen mükemmel cevaba dayanarak. Zaman aşımı komutunun bir ön plan seçeneği vardır, kullanılırsa CTRL + C zaman aşımı komutundan çıkar.

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

Bu harika bir cevap - kabul edilen cevaptan daha basit ve timeoutGNU coreutils kullanarak benim için iyi çalıştı .
RichVel

1

Temelde Ctrl+ Cbir SIGINTsinyal gönderirken Ctrl+ Z bir SIGTSTPsinyal gönderir.

SIGTSTPsadece süreci durdurur, a SIGCONTdevam eder.

Bu, komut satırında çatallanan önplan işlemi üzerinde çalışır.

İşleminiz bir arka plan işlemiyse, bu sinyali işlemlere farklı bir şekilde göndermeniz gerekir. killbunu yapacak. Teorik olarak, bu öldürme üzerine bir "-" - operatörü de alt süreçleri işaret etmelidir, ancak bu nadiren beklendiği gibi çalışır.

Daha fazla okumak için: Unix-Signals


Bu doğrudur, en azından “beklendiği gibi nadiren çalışır” (beklentilerinize bağlıdır - süreç gruplarını okumalısınız), ancak tamamen ilgisiz. Soru, SIGINT ve SIGTSTP hakkında herhangi bir karışıklığa ihanet etmiyor.
Gilles 'SO- kötü olmayı bırak'

0

Mevcut komut dosyaları için "bul ve değiştir" yaklaşımı sağlayarak @Gilles'ın yanıtını iyileştirme :

  1. Bu snippet'i kodunuzun başına ekleyin:
declare -a timeout_pids
exec 21>&1; exec 22>&2 # backup file descriptors, see https://superuser.com/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. Aşağıdaki kodu INTişleyicinize ekleyin (veya birleştirin) :
pre_cleanup(){
    exec 1>&21; exec 2>&22 # restore file descriptors, see https://superuser.com/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. timeoutKomutları my_timeoutkomut dosyanızdaki işlevle değiştirin .

Misal

İşte bir örnek betik:

#!/bin/bash

# see "killing timeout": https://unix.stackexchange.com/a/57692/65781
declare -a timeout_pids
exec 21>&1; exec 22>&2 # backup file descriptors, see https://superuser.com/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 https://superuser.com/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 
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.