potwierdzone wyjście za pomocą pułapki

9

Próbuję przechwycić Ctrl+Csygnał, prosząc użytkownika o potwierdzenie. Część wychwytująca działa dobrze. Ale gdy sygnał zostanie uwięziony, nie wraca do normalnego wykonania. Zamiast tego wychodzi ze skryptu. Jak go wznowić, gdy użytkownik naciśnie „nie”.

oto mój kod

hell()
{
echo "Do you want to quit? Press 1 for yes and 0 for no";
read n;
if [ $n == 1 ]; then
exit 1;
fi
}

trap "hell" SIGINT

find /
CHID
źródło

Odpowiedzi:

12

Co się dzieje

Po naciśnięciu Ctrl+ CThe SIGINTsygnał dostarczany jest do całej grupy procesów planie . Tutaj jest wysyłany zarówno do findprocesu, jak i do procesu powłoki wywołującej. findreaguje natychmiastowym wyjściem, a powłoka reaguje na wywołanie pułapki.

Jeśli kod w pułapce zostanie zwrócony (tzn. Nie wywołuje exit), wykonanie następuje po poleceniu, które zostało przerwane przez sygnał. Tutaj po findkomendzie następuje koniec skryptu, więc skrypt i tak natychmiast się kończy. Ale możesz zobaczyć różnicę między wprowadzeniem 0 i 1, dodając kolejne polecenie:

find /
echo "find returned $?"

Sposób na robienie tego, co chcesz (ale prawdopodobnie nie powinieneś)

Możesz robić co chcesz; ale ta część mojej odpowiedzi dotyczy bardziej odkrywania programowania powłoki niż rozwiązywania rzeczywistego problemu.

  • Ze względów projektowych sygnał, który można zrestartować, nie jest tym, czego normalnie można oczekiwać w przypadku stosunkowo prostych programów, którymi zwykle są skrypty powłoki. Oczekuje się, że Ctrl+ Czabije skrypt.
  • Jak zobaczysz poniżej, i tak rozszerza możliwości powłoki.

Jeśli chcesz uniknąć zabijania find, trzeba go uruchomić w tle : find / &. Następnie użyj waitwbudowanego programu, aby poczekać, aż zakończy się normalnie. Sygnał przerwie waitwbudowane, które można uruchomić w pętli, dopóki nie otrzymasz sygnału, który chcesz propagować. Następnie użyj, killaby zabić pracę.

hell () {
  echo "Do you want to quit? Press 1 for yes and 0 for no"
  read n
  if [ "$n" = 1 ]; then
    # Kill the job if it's running, then exit
    if [ -n "$job_pid" ]; then kill $job_pid; fi
    exit 1
  fi
}

job_pid=
trap "hell" SIGINT

# Start a demo job in the background
for i in 1 2 3 4 5; do date; sleep 1; done &
job_pid=$!
# Call wait in a loop; wait will return 0 if the job exits, and 128+$signum if interrupted by a signal.
while ! wait; do
  echo "resuming wait"
done
job_pid=
echo last exit code: $?

Istnieją ograniczenia tego podejścia w powłoce:

  • Jest warunek wyścigu: jeśli naciśniesz Ctrl+ Ctuż po zakończeniu zadania, ale przed job_pid=linią, program obsługi sygnału spróbuje zabić $jobpid, ale proces już nie istnieje (nawet jako zombie, ponieważ waitjuż go wykorzystał), a proces Identyfikator mógł zostać ponownie wykorzystany w innym procesie. Nie jest to łatwe do naprawienia w powłoce (może przez ustawienie modułu obsługi dla SIGCHLD?).
  • Jeśli potrzebujesz statusu zwrotu z pracy, musisz skorzystać z wait $job_pidformularza. Ale wtedy nie można odróżnić „ waitzostał przerwany przez sygnał” od „zadania zostało zabite przez sygnał” (ani od „zadania zakończonego z własnej woli ze statusem zwrotu ≥128”, ale jest to ogólny fakt w skorupie programowanie).
  • Nie rozszerzy się to łatwo, jeśli w ogóle, na wiele podwiązek. Zauważ, że zachowanie pułapek i sygnałów jest często zaskakujące, gdy wykraczasz poza podstawy w większości implementacji powłoki (tylko ksh robi to dobrze).

Aby pokonać te ograniczenia, użyj bardziej wyszukanego języka, takiego jak Perl lub Python.

Gilles „SO- przestań być zły”
źródło