2013-08-16 14 views
9

Mam bash, który uruchamia niekończące polecenia jako procesów w tle:hak zamykający bash; lub zabicie wszystkich procesów w tle, gdy główny proces ginie

#!/bin/bash 

function xyz() { 
    # some awk command 
} 

endlesscommand "param 1" | xyz & # async 
pids=$! 
endlesscommand "param 2" | xyz & # async 
pids="$pids "$! 
endlesscommand "param 3" | xyz # sync so the script doesn't leave 

Jedynym sposobem, aby zatrzymać tego skryptu jest (musi być) Ctrl-C lub zabić i kiedy to się stanie, muszę zabić wszystkie procesy w tle wymienione w zmiennej $ pids. Jak to zrobić?

Gdyby to było możliwe, aby złapać sygnał kill na głównego procesu i wykonania funkcji, gdy tak się dzieje (hak zamykania), chciałbym zrobić coś takiego:

for $pid in $pids; do kill $pid; done; 

Ale nie mogę znaleźć sposobu to zrobić ...


ROZWIĄZANIE z następujących stanowisk:

#!/bin/bash 

function xyz() { 
# some awk command 
} 

trap 'jobs -p | xargs kill' EXIT # part of the solution 

endlesscommand "param 1" | xyz & # async 
endlesscommand "param 2" | xyz & # async 
endlesscommand "param 3" | xyz & # don't sync, but wait: 

# other part of the solution: 
while pgrep -P "$BASHPID" > /dev/null; do 
    wait 
done 
+0

Musisz używać pułapek ... – devnull

+0

Nie ma potrzeby utrzymywania jawnej listy procesów potomnych; możesz wywołać 'jobs -p', aby uzyskać identyfikatory procesów dzieci, które wciąż są uruchomione. – chepner

Odpowiedz

10

Oto pułapka że nie trzeba śledzić pids:

trap 'jobs -p | xargs kill' EXIT 

EDIT: @Barmar pytanie, czy działa to w skryptach niezasilanych, w których kontrola pracy zwykle nie jest dostępna. To robi. Rozważmy następujący scenariusz:

$ cat no-job-control 
#! /bin/bash 

set -e -o pipefail 

# Prove job control is off 
if suspend 
then 
    echo suspended 
else 
    echo suspension failed, job control must be off 
fi 

echo 

# Set up the trap 
trap 'jobs -p | xargs kill' EXIT 

# Make some work 
(echo '=> Starting 0'; sleep 5; echo '=> Finishing 0') & 
(echo '=> Starting 1'; sleep 5; echo '=> Finishing 1') & 
(echo '=> Starting 2'; sleep 5; echo '=> Finishing 2') & 

echo "What's in jobs -p?" 
echo 

jobs -p 

echo 
echo "Ok, exiting now" 
echo 

Po uruchomieniu widzimy PID trzech grup liderów, a następnie zobaczyć je zabity:

$ ./no-job-control 
./no-job-control: line 6: suspend: cannot suspend: no job control 
suspension failed, job control must be off 

=> Starting 0 
What's in jobs -p? 
=> Starting 1 

54098 
54099 
54100 

Ok, exiting now 

=> Starting 2 
./no-job-control: line 31: 54098 Terminated: 15   (echo '=> Starting 0'; sleep 5; echo '=> Finishing 0') 
./no-job-control: line 31: 54099 Terminated: 15   (echo '=> Starting 1'; sleep 5; echo '=> Finishing 1') 
./no-job-control: line 31: 54100 Terminated: 15   (echo '=> Starting 2'; sleep 5; echo '=> Finishing 2') 

Gdybyśmy zamiast zakomentuj trap linii i ponownie metę trzy zadania nie umierają, a kilka sekund później wydrukują ostatnie wiadomości. Zauważ zwrócony wiersz z przeplotem z końcowymi wyjściami.

$ ./no-job-control 
./no-job-control: line 6: suspend: cannot suspend: no job control 
suspension failed, job control must be off 

=> Starting 0 
What's in jobs -p? 

54110 
54111 
54112 
=> Starting 1 

Ok, exiting now 

=> Starting 2 
$ => Finishing 0 
=> Finishing 2 
=> Finishing 1 
+1

Dzięki, działa świetnie, nawet z rurami! :) i może być nawet krótszy: trap 'jobs -p | xargs kill 'EXIT – fabien

+0

Czy 'jobs-p' działa naprawdę w skryptach, nawet jeśli nie mają włączonej kontroli zadań? – Barmar

+0

@Barmar Zobacz zmiany. – phs

0
kill `ps axl | grep "endlesscommand" | awk '{printf $4" "}'` 

to będzie wyglądać dla procesów nadrzędnych, które ma wpływ na "endlesscommand"

+0

'kill -9' nie jest używane lekko. Wystarczy proste "zabicie". Ponadto, to zabija * wszystkie * procesy uruchamiające 'endlesscommand', nie tylko te rozpoczęte przez rodzica, który jest zabijany. – chepner

+0

@chepner Nie jest poprawny dla procesów spawnowanych ... OP chce zabić wszystkie zaangażowane procesy, więc nie rozumiem, dlaczego 'kill -9' jest niebezpieczne? – iamauser

+0

'kill -9' nie daje zabitemu procesowi szansy na posprzątanie po sobie: zamknij pliki, spłukuj bufory itp. – chepner

1

Można użyć funkcji pgrep i funkcji do zabicia wszystkich procesów utworzonych w ramach głównego procesu, takiego jak ten. To nie tylko zabiłoby bezpośrednie procesy potomne, ale także te stworzone pod nim.

#!/bin/bash 

function killchildren { 
    local LIST=() IFS=$'\n' A 
    read -a LIST -d '' < <(exec pgrep -P "$1") 
    local A SIGNAL="${2:-SIGTERM}" 
    for A in "${LIST[@]}"; do 
     killchildren_ "$A" "$SIGNAL" 
    done 
} 

function killchildren_ { 
    local LIST=() 
    read -a LIST -d '' < <(exec pgrep -P "$1") 
    kill -s "$2" "$1" 
    if [[ ${#LIST[@]} -gt 0 ]]; then 
     local A 
     for A in "${LIST[@]}"; do 
      killchildren_ "$A" "$2" 
     done 
    fi 
} 

trap 'killchildren "$BASHPID"' EXIT 

endlesscommand "param 1" & 
endlesscommand "param 2" & 
endlesscommand "param 3" & 

while pgrep -P "$BASHPID" >/dev/null; do 
    wait 
done 

Co do oryginalnego kodu, byłoby lepiej po prostu korzystać z tablic, a także nie trzeba użyć pętli for:

#!/bin/bash 

trap 'kill "${pids[@]}"' EXIT 

pids=() 
endlesscommand "param 1" & # async 
pids+=("$!") 
endlesscommand "param 2" & # async 
pids+=("$!") 
endlesscommand "param 3" & # syncing this is not a good idea since if the main process would end along with it if it ends earlier. 
pids+=("$!") 

while pgrep -P "$BASHPID" >/dev/null; do 
    wait 
done 

Oryginalny numer funkcja: http://www.linuxquestions.org/questions/blog/konsolebox-210384/bash-functions-to-list-and-kill-or-send-signals-to-process-trees-34624/

+0

Próbowałem tego ostatniego rozwiązania i działa ... ale nie, jeśli mój endlesscommand jest podłączony do jakiegoś innego programu/funkcji (zaktualizowałem moje pytanie). W końcu używam twojego rozwiązania pgrep loop & phs i działa bardzo dobrze. :) – fabien

Powiązane problemy