Ctrl + C ile bir bash betiği durdurulamıyor


42

Tarihi basmak ve uzaktaki bir makineye ping yapmak için basit bir bash betiği yazdım:

#!/bin/bash
while true; do
    #     *** DATE: Thu Sep 17 10:17:50 CEST 2015  ***
    echo -e "\n*** DATE:" `date` " ***";
    echo "********************************************"
    ping -c5 $1;
done

Bir terminalden çalıştırdığımda onu durduramıyorum Ctrl+C. Görünüşe göre ^Cterminale gönderir , ancak senaryo durmaz.

MacAir:~ tomas$ ping-tester.bash www.google.com

*** DATE: Thu Sep 17 23:58:42 CEST 2015  ***
********************************************
PING www.google.com (216.58.211.228): 56 data bytes
64 bytes from 216.58.211.228: icmp_seq=0 ttl=55 time=39.195 ms
64 bytes from 216.58.211.228: icmp_seq=1 ttl=55 time=37.759 ms
^C                                                          <= That is Ctrl+C press
--- www.google.com ping statistics ---
2 packets transmitted, 2 packets received, 0.0% packet loss
round-trip min/avg/max/stddev = 40.887/59.699/78.510/18.812 ms

*** DATE: Thu Sep 17 23:58:48 CEST 2015  ***
********************************************
PING www.google.com (216.58.211.196): 56 data bytes
64 bytes from 216.58.211.196: icmp_seq=0 ttl=55 time=37.460 ms
64 bytes from 216.58.211.196: icmp_seq=1 ttl=55 time=37.371 ms

Ne kadar basarsam ya da ne kadar hızlı yaparsam yapayım. Onu durduramıyorum.
Testi yapın ve kendiniz gerçekleştirin.

Yan bir çözüm olarak, onu Ctrl+Zdurduruyorum, durdu ve sonra kill %1.

Burada tam olarak ne oluyor ^C?

Yanıtlar:


26

Ne olur hem olmasıdır bashve pingSIGINT almak ( bashvarlık değil interaktif, hem pingve bashbunu kimden senaryoyu ran interaktif kabuk tarafından terminalin ön plan süreci grubu olarak oluşturulan ve set edilmiş aynı işlem grubunda çalıştırın).

Ancak, bashSIGINT'in eşzamansız olarak, yalnızca o anda çalışan komuttan çıktıktan sonra ilgilenir. bashsadece o anda çalışan komut bir SIGINT’ten ölürse, yani çıkış durumu SIGINT tarafından öldürüldüğünü gösterirse, bu SIGINT’i aldıktan sonra çıkar.

$ bash -c 'sh -c "trap exit\ 0 INT; sleep 10; :"; echo here'
^Chere

Yukarıda, ve bash, Ctrl-C tuşlarına bastığımda SIGINT alıyorum, ancak normalde 0 çıkış koduyla çıkıyor, bu nedenle SIGINT'i yok sayıyor, bu yüzden "burada" görüyoruz.shsleepshbash

pingEn azından iputillerden biri böyle davranır. Kesildiğinde, istatistikleri yazdırır ve ping'lerinin cevaplanıp cevaplanmamasına bağlı olarak 0 veya 1 çıkış durumundan çıkar. Sırasında Ctrl-C tuşlarına basın Yani, ping, çalıştığı bashbastığınız ettik notlar Ctrl-Cama onun SIGINT işleyicilerindeki beri pingnormalde çıkışlar, bashçıkış vermez.

Eğer sleep 1bu döngüye bir giriş ekler ve Ctrl-Cçalışırken basın sleep, çünkü sleepSIGINT üzerinde özel bir işleyicisi yoktur, ölecek ve bashSIGINT'ten öldüğünü bashbildirecek ve bu durumda çıkacak (aslında SIGINT ile kendini öldürecek. kesintiyi ebeveyne bildirmek için).

Neden bashböyle davrandığına gelince , emin değilim ve davranışların her zaman belirleyici olmadığını not ediyorum. Ben sadece sordunuz üzerinde soru bashgeliştirme posta listesine ( Güncelleme : @Jilles şimdi nedenle aşağı çivilenmiş olan onun cevabını ).

Benzer davrandığını bulduğum diğer tek kabuk ksh93'tür (@Jilles tarafından belirtildiği gibish güncelleme, FreeBSD de öyle ). Orada, SIGINT açıkça göz ardı ediliyor gibi görünüyor. Ve ksh93çıkışlar bir komut SIGINT tarafından öldürülür zaman.

bashYukarıdaki gibi aynı davranışı elde edersiniz, aynı zamanda:

ksh -c 'sh -c "kill -INT \$\$"; echo test'

"Test" yazmıyor. Başka bir deyişle, SIGINT ile bekleyen komut, SIGINT'in ölümünü beklemişse, kendisi de o SIGINT'i almadıysa, çıkar.

Etrafta bir çalışma, şunu eklemek olacaktır:

trap 'exit 130' INT

bashBir SIGINT aldıktan sonra çıkmaya zorlamak için komut dosyasının en üstünde ( her durumda, SIGINT'in yalnızca şu anda çalışan komuttan çıktıktan sonra, eşzamanlı olarak işlenmeyeceğini unutmayın).

İdeal olarak, ebeveynimize bir SIGINT'ten öldüğümüzü bildirmek isteriz ( bashörneğin, başka bir komut dosyası varsa , o bashkomut dosyası da kesilir). Bir exit 130SIGINT'in ölmesiyle aynı değildir (bazı mermiler her $?iki durumda da aynı değere ayarlanmış olsa da ), ancak SIGINT'in ölümünü bildirmek için kullanılır (SIGINT'in en fazla 2 olduğu sistemlerde).

Ancak bash, ksh93veya FreeBSD için shbu işe yaramaz. 130 çıkış durumunun SIGINT tarafından bir ölüm olarak görülmediği ve bir ebeveyn betiğinin iptal edilmeyeceği belirtildi .

Bu nedenle, muhtemelen daha iyi bir alternatif, SIGINT'i aldıktan sonra SIGINT ile kendimizi öldürmek olacaktır:

trap '
  trap - INT # restore default INT handler
  kill -s INT "$$"
' INT

1
jills'ın cevabı “neden” i açıklıyor. Bir de açıklayıcı örnekte, dikkate  for f in *.txt; do vi "$f"; cp "$f" newdir; done. Kullanıcı dosyalardan birini düzenlerken Ctrl + C tuşlarına basarsa, viyalnızca bir mesaj görüntüler. Kullanıcı dosyayı düzenlemeyi bitirdikten sonra döngünün devam etmesi makul görünüyor. (Ve evet, söyleyebileceğinizi biliyorum vi *.txt; cp *.txt newdir; sadece fordöngüyü örnek olarak sunuyorum .)
Scott

@Scott, iyi nokta. Her ne kadar vi( vimen azından iyi ) isigdüzenleme sırasında tty'yi devre dışı bırakır ( :!cmdgerçi koşarken açık bir şekilde yapmaz ve bu durumda bu çok geçerli olacaktır).
Stéphane Chazelas 19:15

@Tim, düzenlemenizdeki düzeltme için düzenlememe bakın.
Stéphane Chazelas

@ StéphaneChazelas Teşekkürler. Bu yüzden pingSIGINT'i aldıktan sonra 0 ile çıkılıyor. Benzer bir davranış sudoyerine bash betiği içeriyorsa ping, ancak sudoSIGINT'i aldıktan sonra 1 ile çıkıyorum. unix.stackexchange.com/questions/479023/…
Tim

13

Bu açıklama, bash'ın http://www.cons.org/cracauer/sigint.html uyarınca SIGINT ve SIGQUIT için WCE (bekle ve işbirliğine dayalı çıkış) uyguladığını göstermektedir . Bu, eğer bash bir işlemin bitmesini beklerken SIGINT veya SIGQUIT alırsa, işlem çıkana kadar bekleyecek ve işlem bu sinyalden çıktığında kendiliğinden çıkacaktır. Bu, kullanıcı arayüzünde SIGINT veya SIGQUIT kullanan programların beklendiği gibi çalışmasını sağlar (sinyal programın sonlanmasına neden olmadıysa, komut dosyası normal şekilde devam eder).

SIGINT veya SIGQUIT'i yakalayan, ancak daha sonra sonlandıran, ancak sinyali kendilerine yeniden göndermek yerine normal bir exit () kullanarak programlarda bir dezavantaj belirir. Bu tür programları çağıran komut dosyalarını kesmek mümkün olmayabilir. Sanırım asıl düzeltmenin ping ve ping6 gibi programlarda olduğunu düşünüyorum.

Benzer davranış ksh93 ve FreeBSD'ler / bin / sh tarafından uygulanır, ancak diğer birçok kabuk tarafından gerçekleştirilmez.


Teşekkürler, bu çok mantıklı. FreeBSD sh'in ya cmd'nin çıkışla (130) çıkmasından sonra da iptal edilmediğini, bu da bir çocuğun SIGINT'inden ölümü bildirmenin yaygın bir yoludur (mksh, exit(130)örneğin kesirseniz mksh -c 'sleep 10;:').
Stéphane Chazelas 19:15

5

Tahmin ettiğiniz gibi, bu SIGINT'in ikincil işleme gönderilmesi ve bu işlemin ardından devam eden kabuk nedeniyledir.

Bunu daha iyi ele almak için, çalışan komutların çıkış durumunu kontrol edebilirsiniz. Unix dönüş kodu, hem bir işlemden çıkma yöntemini (sistem çağrısı veya sinyali) hem de hangi değerin geçtiğini exit()veya işlemi hangi sinyalin sonlandırdığını kodlar . Bunların hepsi oldukça karmaşıktır, ancak onu kullanmanın en hızlı yolu, sinyalle sonlandırılan bir işlemin sıfır olmayan bir dönüş koduna sahip olacağını bilmektir. Böylece, betiğinizdeki dönüş kodunu kontrol ederseniz, çocuk işlemi sonlandırıldığında, gereksiz sleepçağrılar gibi devralmalara duyulan ihtiyacı ortadan kaldırarak kendinizden çıkabilirsiniz . Bunu betiğiniz boyunca yapmanın hızlı bir yolu kullanmaktır set -e, bununla birlikte çıkış durumu sıfırdan farklı olan komutlar için birkaç ayar gerekebilir.


1
Set -e bash-4 kullanmıyorsanız bash içinde düzgün çalışmıyor
schily 18:15

Ne demek "düzgün çalışmıyor"? Bash 3'te başarılı bir şekilde kullandım, ancak büyük olasılıkla bazı durumlar var.
Tom Hunt

Birkaç basit durumda bash3 hatayla çıktı. Ancak bu, genel davada gerçekleşmedi. Tipik bir sonuç olarak, bir hedef oluştururken başarısızlık yapılamadı ve bu, alt dizinlerdeki hedefler listesinde çalışan makefile'den kaynaklanıyordu. David Korn ve ben, bash4 için hatayı düzeltmeye ikna etmesi için bash bekçisiyle birkaç hafta posta yapmak zorunda kaldık.
schily

4
Buradaki sorunun, pingSIGINT aldıktan bashsonra 0 çıkış durumuyla geri döndüğünü ve bu durumda, aldığı SIGINT'i görmezden geldiğini unutmayın. Bir "set -e" eklemek veya çıkış durumunu kontrol etmek burada yardımcı olmaz. SIGINT'e açık bir tuzak eklemek yardımcı olacaktır.
Stéphane Chazelas 18:15

4

Terminal, control-c'yi fark eder ve yeni bir INTön plan işlem grubu oluşturmadığı için, kabuğu içeren ön plan işlem grubuna bir sinyal gönderir ping. Bu, yakalayarak doğrulamak kolaydır INT.

#! /bin/bash
trap 'echo oh, I am slain; exit' INT
while true; do
  ping -c5 127.0.0.1
done

Çalıştırılan komut yeni bir ön plan işlem grubu oluşturduysa, control-c bu işlem grubuna gidecektir, kabuğa değil. Bu durumda, kabuk terminal tarafından işaret edilmeyeceğinden çıkış kodlarını incelemeye ihtiyaç duyacaktır.

( INTKabukları içinde işleme kabuk bazen sinyal yok saymak gerekir olarak inanılmaz, bu arada, karmaşık ve bazen Kaynak dalış merak halinde veya ponder olabilir. tail -f /etc/passwd; echo foo)


Bu durumda, sorun sinyal işleme değil, senaryoda bash'nin jobcontrol yapması gerektiği
gerçeğidir

SIGINT'in yeni işlem grubuna gitmesi için komutun, terminalin ön plan işlem grubu olması için terminale bir ioctl () yapması gerekirdi. pingburada yeni bir süreç grubu başlatmak için hiçbir neden yoktur ve OP'nin sorununu yeniden üretebileceğim ping sürümü (Debian'da iputils) bir işlem grubu oluşturmaz.
Stéphane Chazelas

1
SIGINT'i gönderen terminalin olmadığını unutmayın, tty aygıtının (/ dev / ttysomething aygıtının sürücüsü (çekirdeğin kodu) / çıkmaz) alındığında (genellikle lnext ile ^ V) ^ C karakterinin satır disiplini olduğunu unutmayın. Terminalden
Stéphane Chazelas 18:15

2

Eh, sleep 1bash betiğine bir eklemeye çalıştım ve bang!
Şimdi iki ile durdurabiliyorum Ctrl+C.

Basıldığında Ctrl+C, bir SIGINTsinyal döngü içinde çalıştırıldı komut şu anda yürütülen süreç, gönderilir. Ardından, alt kabuk işlemi döngüdeki bir sonraki komutu çalıştırmaya devam eder, bu başka bir işlemi başlatır. Komut dosyasını durdurabilmek için SIGINTbiri yürütmede geçerli komutu kesmek için diğeri alt kabuk işlemini kesmek için iki sinyal göndermek gerekir .

Çağrısız komut dosyasında sleep, Ctrl+Cgerçekten hızlı ve birçok kez basmak işe yaramaz gibi görünüyor ve döngüden çıkmak mümkün değil. Tahminime göre, iki kez basmak, mevcut yürütülen işlemin kesilmesi ile bir sonraki işlemin başlangıcı arasında doğru zamanda yapmak için yeterince hızlı değildir. Her Ctrl+Cbasıldığında SIGINTdöngü içerisinde yürütülen bir işleme, ancak alt kabuğa değil .

Betiği ile sleep 1, bu çağrı bir saniye için yürütmeyi askıya alır ve ilk Ctrl+C(ilk SIGINT) tarafından kesildiğinde , alt kabuk bir sonraki komutu çalıştırmak için daha fazla zaman alır. Şimdi, ikinci Ctrl+C(ikinci SIGINT) alt kabuğa gidecek ve komut dosyası uygulaması sona erecek.


Hatalısınız, doğru çalışan bir kabukta, tek bir ^ C yeterlidir, arka plan için cevabımı görün.
schily

Eh, aşağı oy kullandığınızı ve şu anda cevabınızın -1 puan aldığını göz önünde bulundurarak, cevabınızı ciddiye almam konusunda çok ikna olmadım.
yeğeni

Bazı kişilerin aşağı oy vermesi her zaman bir cevabın kalitesiyle ilgili değildir. İki kere yazmanız gerekiyorsa, ^ c, kesinlikle bir bash böceğinin kurbanı olacaksınız. Farklı bir kabuk denedin mi? Gerçek Bourne Shell'i denedin mi?
schily

Eğer kabuk düzgün çalışıyorsa, aynı işlem grubundaki bir komut dosyasındaki her şeyi çalıştırır ve sonra tek bir ^ c yeterlidir.
schily

1
Bu cevabın @nephewtom tanımındaki davranışı, Ctrl-C aldıklarında betiğin farklı davranışıyla açıklanabilir. Bir uyku varsa, ezici bir şekilde, uyku çalışırken Ctrl-C'nin alınması olasıdır (döngüdeki her şeyin hızlı olduğu varsayılırsa). Uyku değeri çıkış 130 ile öldürülür. Uykunun ebeveyni, bir kabuk, uykunun imzayla öldürüldüğünü fark eder ve çıkar. Ancak senaryo uyku içermiyorsa, Ctrl-C ping'e gider, bunun yerine 0 ile çıkarak tepki verir, böylece ana kabuk bir sonraki komutu çalıştırmaya devam eder.
Jonathan Hartley

0

Bunu dene:

#!/bin/bash
while true; do
   echo "Ctrl-c works during sleep 5"
   sleep 5
   echo "But not during ping -c 5"
   ping -c 5 127.0.0.1
done

Şimdi ilk satırı şu şekilde değiştirin:

#!/bin/sh

ve tekrar deneyin - ping'in artık kesilebilir olup olmadığını görün.


0
pgrep -f process_name > any_file_name
sed -i 's/^/kill /' any_file_name
chmod 777 any_file_name
./any_file_name

örneğin pgrep -f firefox, çalışan PID'yi aşacak firefoxve bu PID'yi bir dosyaya kaydedecektir any_file_name. 'sed' komutu kill'any_file_name' dosyasına PID numarasının başına ekleyecektir . Üçüncü satır any_file_nameçalıştırılabilir dosya olacaktır . Şimdi satır, dosyada mevcut olan PID'i öldürür any_file_name. Yukarıdaki dört satırı bir dosyaya yazmak ve dosyayı çalıştırmak Control- C. Benim için kesinlikle iyi çalışıyor.


0

Herhangi biri bu bashözellik için bir düzeltme yapmak istiyorsa ve arkasındaki felsefe ile ilgilenmiyorsa , işte size bir öneri:

Şirketinden sorunlu komutu ancak a) b sonlandırmak için) bekler etmez) sinyalleri ve c karışıklık yapan bir sargıdan verme değil , sadece WCE mekanizması kendisini uygulamak, fakat alınması üzerine ölür SIGINT.

Böyle bir sargı işlevi awk+ ile yapılabilir system().

$ while true; do awk 'BEGIN{system("ping -c5 localhost")}'; done
PING localhost(localhost (::1)) 56 data bytes
64 bytes from localhost (::1): icmp_seq=1 ttl=64 time=0.082 ms
64 bytes from localhost (::1): icmp_seq=2 ttl=64 time=0.087 ms
^C
--- localhost ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 1022ms
rtt min/avg/max/mdev = 0.082/0.084/0.087/0.009 ms
[3]-  Terminated              ping -c5 localhost

OP's gibi bir komut dosyası koyun:

#!/bin/bash
while true; do
        echo -e "\n*** DATE:" `date` " ***";
        echo "********************************************"
        awk 'BEGIN{system(ARGV[1])}' "ping -c5 ${1-localhost}"
done

-3

Sen iyi bilinen bir bash böceğinin kurbanısın. Bash, komut dosyası için hata yapıyor, iş kontrolü yapar.

Olduğu şey, bash'ın harici programları komut dosyasının kendisi için kullandığından farklı bir işlem grubunda çalıştırmasıdır. TTY işlem grubu, mevcut ön plan işleminin işlem grubuna ayarlandığı için sadece bu ön plan işlemi öldürülür ve kabuk betiğindeki döngü devam eder.

Doğrulamak için: pgrp (1) işlevini yerleşik bir program olarak uygulayan yeni bir Bourne Shell'i alın ve derleyin, ardından komut dosyası döngüsüne bir / bin / uyku 100 (veya / usr / bin / uyku) ekleyin ve ardından komut dosyasını başlatın. Bourne Shell. Uyku komutu ve betiği çalıştıran bash için işlem kimliklerini elde etmek için ps (1) kullandıktan sonra, uykunun işlem kimliğini ve betiği çalıştıran bash ile pgrp <pid>"<pid>" işlevini çağırın ve değiştirin. Farklı işlem grubu kimliklerini göreceksiniz. Şimdi pgrp < /dev/pts/7, geçerli tty işlem grubunu elde etmek için şuna benzer bir şey çağırın (betiğin kullandığı tty ismini yazın). TTY işlem grubu, uyku komutunun işlem grubuna eşittir.

Düzeltmek için: farklı bir kabuk kullanın.

En yeni Bourne Shell kaynakları burada bulabileceğiniz schily araçları paketimde:

http://sourceforge.net/projects/schilytools/files/


Bu hangi versiyon bash? AFAIK, bashyalnızca -m veya -i seçeneğini iletirseniz bunu yapar.
Stéphane Chazelas

Artık bash4 için geçerlidir ancak OP gibi sorunları vardır, o bash 3'e kullanmak gibi görünen görünüyor
Schily

Bash3.2.48 ile bash 3.0.16 veya bash-2.05b ile çoğaltılamaz (denenmiştir bash -c 'ps -j; ps -j; ps -j').
Stéphane Chazelas

Bu kesinlikle bash dediğiniz zaman olur /bin/sh -ce. Katmanlı bir arama çağrısını iptal smakeetmek için o sırada çalışan bir komut için işlem grubunu açıkça öldüren çirkin bir geçici çözüm eklemek zorunda kaldım ^C. Bash'ın işlem grubunu başlatıldığı işlem grubu kimliğinden değiştirip değiştirmediğini kontrol ettiniz mi?
schily

ARGV0=sh bash -ce 'ps -j; ps -j; ps -j'her 3 ps çağrısında ps ve bash için aynı pgid raporunu veriyor. (ARGV0 = sh, zshargv [0] 'ı geçmenin yoludur).
Stéphane Chazelas 18:15
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.