Co się dzieje, że zarówno bash
i ping
otrzyma SIGINT ( bash
istota nie interaktywne, zarówno ping
i bash
działać w tej samej grupie procesów, które zostały utworzone i ustawione jako grupy procesów planie terminala przez interaktywnej powłoki uruchomiono ten skrypt z).
Jednak bash
obsługuje SIGINT asynchronicznie, dopiero po zakończeniu aktualnie uruchomionej komendy. bash
kończy działanie dopiero po otrzymaniu tego SIGINT, jeśli aktualnie uruchomione polecenie umiera z SIGINT (tzn. jego status wyjścia wskazuje, że został zabity przez SIGINT).
$ bash -c 'sh -c "trap exit\ 0 INT; sleep 10; :"; echo here'
^Chere
Powyżej bash
, sh
i sleep
otrzymać SIGINT po naciśnięciu Ctrl-C, ale sh
wychodzi zwykle z kodem 0 wyjścia, więc bash
pomija SIGINT, dlatego widzimy „tutaj”.
ping
, przynajmniej ten z iputils, zachowuje się tak. Po przerwaniu drukuje statystyki i wychodzi ze statusem wyjścia 0 lub 1 w zależności od tego, czy odpowiedzi na ping zostały wysłane. Tak więc, gdy naciśniesz Ctrl-C podczas ping
działania, bash
notatki, które nacisnąłeś Ctrl-C
w jego procedurach obsługi SIGINT, ale ponieważ ping
wychodzą normalnie, bash
nie wychodzą.
Jeśli dodasz sleep 1
w tej pętli i naciśniesz Ctrl-C
podczas sleep
działania, ponieważ sleep
nie ma specjalnego modułu obsługi w SIGINT, umrze i zgłosi bash
, że zmarł w wyniku SIGINT, i w takim przypadku bash
zakończy działanie (faktycznie zabije się za pomocą SIGINT, tak że zgłosić przerwanie rodzicowi).
Co do tego, dlaczego bash
tak się zachowuje, nie jestem pewien i zauważam, że zachowanie nie zawsze jest deterministyczne. Właśnie zadałem pytanie na bash
liście mailingowej dla programistów ( aktualizacja : @Jilles podał teraz powód swojej odpowiedzi ).
Jedyną znalezioną przeze mnie powłoką, która zachowuje się podobnie, jest ksh93 (aktualizacja, o której wspomina @Jilles, podobnie jak FreeBSDsh
). Tam SIGINT wydaje się wyraźnie ignorowany. I ksh93
kończy działanie, gdy polecenie jest zabijane przez SIGINT.
Otrzymujesz takie samo zachowanie jak bash
powyżej, ale także:
ksh -c 'sh -c "kill -INT \$\$"; echo test'
Nie wyświetla „testu”. Oznacza to, że kończy działanie (zabijając się tam za pomocą SIGINT), jeśli polecenie, na które czekał, umiera SIGINT, nawet jeśli samo nie otrzymało tego SIGINT.
Obejściem byłoby dodanie:
trap 'exit 130' INT
W górnej części skryptu, aby wymusić bash
wyjście po otrzymaniu SIGINT (pamiętaj, że w każdym razie SIGINT nie będzie przetwarzany synchronicznie, tylko po zakończeniu aktualnie uruchomionej komendy).
Najlepiej byłoby, gdybyśmy chcieli zgłosić naszemu rodzicowi, że zmarliśmy z powodu SIGINT (aby bash
na przykład w przypadku innego skryptu ten bash
skrypt również został przerwany). Wykonanie an exit 130
to nie to samo, co umieranie SIGINT (chociaż niektóre powłoki ustawią się $?
na tę samą wartość w obu przypadkach), jednak często jest używane do zgłaszania śmierci przez SIGINT (w systemach, w których SIGINT to 2, co jest najbardziej).
Jednak dla bash
, ksh93
lub FreeBSD sh
, że nie działa. Ten status wyjścia 130 nie jest uważany przez SIGINT za śmierć, a skrypt nadrzędny nie przerywałby tam.
Zatem prawdopodobnie lepszą alternatywą byłoby zabicie się SIGINT po otrzymaniu SIGINT:
trap '
trap - INT # restore default INT handler
kill -s INT "$$"
' INT
for f in *.txt; do vi "$f"; cp "$f" newdir; done
. Jeśli użytkownik wpisze Ctrl + C podczas edycji jednego z plików,vi
po prostu wyświetli komunikat. Wydaje się uzasadnione, że pętla powinna być kontynuowana po zakończeniu edycji pliku przez użytkownika. (I tak, wiem, że można powiedziećvi *.txt; cp *.txt newdir
; po prostu przesyłamfor
pętlę jako przykład.)vi
(vim
przynajmniej dobrze ) wyłącza ttyisig
podczas edycji (nie jest to oczywiście przy uruchamianiu:!cmd
, i to bardzo by miało zastosowanie w tym przypadku).ping
kończy się na 0 po otrzymaniu SIGINT. Znalazłem podobne zachowanie, gdy skrypt bash zawierasudo
zamiastping
, alesudo
wychodzi z 1 po otrzymaniu SIGINT. unix.stackexchange.com/questions/479023/…Wyjaśnienie jest takie, że bash implementuje WCE (oczekiwanie i kooperacyjne wyjście) dla SIGINT i SIGQUIT na http://www.cons.org/cracauer/sigint.html . Oznacza to, że jeśli bash otrzyma SIGINT lub SIGQUIT podczas oczekiwania na zakończenie procesu, będzie czekał na zakończenie procesu i sam się opuści, jeśli proces zakończy się na tym sygnale. Zapewnia to, że programy używające SIGINT lub SIGQUIT w interfejsie użytkownika będą działać zgodnie z oczekiwaniami (jeśli sygnał nie spowodował zakończenia programu, skrypt będzie kontynuował normalnie).
Minusem pojawia się w przypadku programów, które wyłapują SIGINT lub SIGQUIT, ale kończą z tego powodu, ale używają normalnego wyjścia () zamiast ponownie wysyłając sygnał do siebie. Przerwanie skryptów wywołujących takie programy może być niemożliwe. Myślę, że prawdziwą poprawką jest w takich programach jak ping i ping6.
Podobne zachowanie jest implementowane przez ksh93 i FreeBSD's / bin / sh, ale nie przez większość innych powłok.
źródło
exit(130)
na przykład, jeśli przerwieszmksh -c 'sleep 10;:'
).Jak można się domyślić, jest to spowodowane wysłaniem SIGINT do podrzędnego procesu i kontynuowaniem powłoki po zakończeniu tego procesu.
Aby lepiej sobie z tym poradzić, możesz sprawdzić status wyjścia z uruchomionych poleceń. Uniksowy kod powrotu koduje zarówno metodę, z której proces zakończył się (wywołanie systemowe lub sygnał), jak i jaka wartość została przekazana
exit()
lub jaki sygnał zakończył proces. To wszystko jest dość skomplikowane, ale najszybszym sposobem użycia jest wiedzieć, że proces zakończony sygnałem będzie miał niezerowy kod powrotu. Tak więc, jeśli sprawdzisz kod powrotu w skrypcie, możesz wyjść z siebie, jeśli proces potomny został zakończony, eliminując potrzebę nieelegancji, takich jak niepotrzebnesleep
wywołania. Szybkim sposobem na zrobienie tego w całym skrypcie jest użycieset -e
, choć może wymagać kilku poprawek dla poleceń, których status wyjścia jest niezerowy.źródło
ping
po otrzymaniu SIGINT wraca ze statusem wyjścia 0, abash
następnie ignoruje otrzymany SIGINT, jeśli tak jest. Dodanie „zestawu -e” lub sprawdzenie statusu wyjścia nie pomoże tutaj. Pomocne byłoby dodanie wyraźnej pułapki w SIGINT.Terminal zauważa kontrolkę-c i wysyła
INT
sygnał do grupy procesów pierwszego planu, która tutaj obejmuje powłokę, ponieważping
nie utworzyła nowej grupy procesów pierwszego planu. Łatwo to zweryfikować za pomocą pułapkiINT
.Jeśli uruchamiane polecenie utworzyło nową grupę procesów pierwszego planu, wtedy control-c przejdzie do tej grupy procesów, a nie do powłoki. W takim przypadku powłoka będzie musiała sprawdzić kody wyjścia, ponieważ nie będzie sygnalizowana przez terminal.
(
INT
Obsługa w powłokach można fabulously skomplikowane, przez sposób, w zależności od powłoki czasami musi zignorować sygnału, a czasami nie Źródło nurkowania jeśli ciekawy lub Ponder.tail -f /etc/passwd; echo foo
)źródło
ping
nie ma powodu, aby założyć tutaj nową grupę procesów, a wersja ping (iputils na Debianie), z którą mogę odtworzyć problem PO, nie tworzy grupy procesów.Próbowałem dodać a
sleep 1
do skryptu bash i huk!Teraz mogę to zatrzymać dwoma Ctrl+C.
Po naciśnięciu przycisku Ctrl+C, A
SIGINT
sygnał jest wysyłany do procesu, który aktualnie wykonywanego komenda została uruchomiona wewnątrz pętli. Następnie proces podpowłoki kontynuuje wykonywanie następnego polecenia w pętli, co uruchamia inny proces. Aby móc zatrzymać skrypt, należy wysłać dwaSIGINT
sygnały, jeden do przerwania bieżącego polecenia w trakcie wykonywania, a drugi do przerwania procesu podpowłoki .W skrypcie bez
sleep
wywołania naciskanie Ctrl+Cnaprawdę szybko i wiele razy wydaje się nie działać i nie można wyjść z pętli. Domyślam się, że dwukrotne naciśnięcie nie jest wystarczająco szybkie, aby zrobić to w odpowiednim momencie między przerwaniem aktualnie wykonywanego procesu a rozpoczęciem następnego. Każde Ctrl+Cnaciśnięcie wyśle aSIGINT
do procesu wykonanego w pętli, ale nie do podpowłoki .W skrypcie z
sleep 1
to wywołanie zawiesi wykonanie na jedną sekundę, a gdy zostanie przerwane przez pierwszą Ctrl+C(pierwsząSIGINT
), podpowłoka zajmie więcej czasu na wykonanie następnego polecenia. Zatem teraz drugi Ctrl+C(drugiSIGINT
) przejdzie do podpowłoki i zakończy się wykonywanie skryptu.źródło
Spróbuj tego:
Teraz zmień pierwszy wiersz na:
i spróbuj ponownie - sprawdź, czy ping jest teraz przerywany.
źródło
na przykład
pgrep -f firefox
zamieni PID uruchomionego programufirefox
i zapisze ten PID w pliku o nazwieany_file_name
. Polecenie „sed” dodakill
początek numeru PID do pliku „nazwa_pliku_ dowolna”. Trzeci wiersz będzieany_file_name
zawierał plik wykonywalny. Teraz czwarta linia zabije PID dostępny w plikuany_file_name
. Zapisanie powyższych czterech wierszy w pliku i wykonanie tego pliku może wykonać Control- C. Działa dla mnie absolutnie dobrze.źródło
Jeśli ktoś jest zainteresowany naprawą tej
bash
funkcji, a nie tyle filozofią, która się za nią kryje , oto propozycja:Nie uruchamiaj problematycznego polecenia bezpośrednio, ale z opakowania, które a) czeka na zakończenie, b) nie zadziera z sygnałami ic) nie implementuje samego mechanizmu WCE, ale po prostu umiera po otrzymaniu
SIGINT
.Takie opakowanie można wykonać za pomocą
awk
+ jegosystem()
funkcji.Umieść skrypt jak OP:
źródło
Jesteś ofiarą znanego błędu bash. Bash wykonuje kontrolę zadań dla skryptów, co jest błędem.
To, co się dzieje, polega na tym, że bash uruchamia programy zewnętrzne w innej grupie procesów niż używa dla samego skryptu. Ponieważ grupa procesów TTY jest ustawiona na grupę procesów bieżącego procesu pierwszego planu, tylko ten proces pierwszego planu zostaje zabity, a pętla w skrypcie powłoki jest kontynuowana.
Aby zweryfikować: pobierz i skompiluj najnowszą powłokę Bourne'a, która implementuje pgrp (1) jako wbudowany program, a następnie dodaj / bin / sleep 100 (lub / usr / bin / sleep w zależności od platformy) do pętli skryptów, a następnie uruchom Bourne Shell. Po użyciu ps (1) w celu uzyskania identyfikatorów procesu dla polecenia sleep i bash, który uruchamia skrypt, wywołaj
pgrp <pid>
i zamień „<pid>” na identyfikator procesu sleep i bash, który uruchamia skrypt. Zobaczysz różne identyfikatory grup procesów. Teraz wywołaj coś w stylupgrp < /dev/pts/7
(zastąp nazwę tty nazwą tty używaną przez skrypt), aby uzyskać bieżącą grupę procesów tty. Grupa procesów TTY jest równa grupie procesów polecenia uśpienia.Aby naprawić: użyj innej powłoki.
Najnowsze źródła Bourne Shell są w moim pakiecie narzędzi schily, które można znaleźć tutaj:
http://sourceforge.net/projects/schilytools/files/
źródło
bash
? AFAIKbash
robi to tylko wtedy, gdy przekażesz opcję -m lub -i.bash -c 'ps -j; ps -j; ps -j'
)./bin/sh -ce
. Musiałem dodać brzydkie obejście,smake
które wyraźnie zabija grupę procesów dla aktualnie uruchomionego polecenia, aby umożliwić^C
przerwanie warstwowego wywołania wykonywania. Czy sprawdziłeś, czy bash zmienił grupę procesów z identyfikatora grupy procesów, którym została zainicjowana?ARGV0=sh bash -ce 'ps -j; ps -j; ps -j'
zgłasza ten sam pgid dla ps i bash we wszystkich 3 wywołaniach ps. (ARGV0 = sh jestzsh
sposobem na przekazanie argv [0]).