2016-03-30 12 views
3

HintergrundBeenden ein Bash-Skript, wenn ein Fehler auftritt, darin oder einer des Hintergrundjobs schafft es

Ich bin auf einem Bash-Skript arbeite den Prozess des Aufbaus eines halbes Dutzend Projekte zu automatisieren, die in dem leben dasselbe Verzeichnis. Jedes Projekt hat zwei Skripte ausführen, um ihn zu bauen:

npm install 
npm run build 

Die erste Zeile wird alle Abhängigkeiten von npm holen. Da dieser Schritt am längsten dauert und die Projekte ihre Abhängigkeiten gleichzeitig abrufen können, verwende ich einen Hintergrundjob, um alles parallel abzurufen. (zB: npm install &)

Die zweite Zeile verwendet diese Abhängigkeiten, um das Projekt zu erstellen. Da dies nach Abschluss aller Schritte von Schritt 1 geschehen muss, führe ich den Befehl wait dazwischen aus. Siehe Codefragment unten.

Die Frage

Ich möchte so schnell mein Skript Ausgang haben, wie ein Fehler in einem der Hintergrundjobs auftritt, oder der npm run build Schritt, der später geschieht.

Ich benutze set -e, aber dies gilt nicht für die Hintergrund-Jobs, und damit, wenn ein Projekt fehlschlägt, seine Abhängigkeiten zu installieren, läuft alles andere weiter.

Hier ist ein vereinfachtes Beispiel, wie mein Skript jetzt aussieht.

build.sh

set -e 

DIR=$PWD 

for dir in ./projects/**/ 
do 
    echo -e "\033[4;32mInstalling $dir\033[0m" 
    cd $dir 
    npm install & # takes a while, so do this in parallel 
    cd $DIR 
done 

wait # continue once the background jobs are completed 

for dir in ./projects/**/ 
do 
    cd $dir 
    echo -e "\033[4;32mBuilding $dir\033[0m" 
    npm run build # Some projects use other projects, so build them in series 
    cd $DIR 
    echo -e "\n" 
done 

Noch einmal, ich will nicht in das Skript etwas zu tun, um fortzufahren, wenn ein Fehler an irgendeiner Stelle auftritt, gilt dies sowohl für die Eltern und Hintergrundjobs. Ist das möglich?

+2

BTW, 'echo -e' ist schlechte Form: die POSIX-Spezifikation für' echo' erfordert 'echo -e', um '-e' auf seiner Ausgabe zu drucken, so dass Sie buchstäblich Code schreiben, der nur den Weg funktioniert Sie beabsichtigen eine * nicht * -konforme Shell. Verwenden Sie 'printf '% b \ n'" Zeichenfolge mit Escape-Sequenzen "', um Inhalt auf eine vollständig tragbare Weise zu drucken. –

+0

Die POSIX-Spezifikation für "echo" finden Sie unter http://pubs.opengroup.org/onlinepubs/009604599/utilities/echo.html. Beachten Sie dabei den Abschnitt APPLICATION USAGE. –

+1

Auch, re: Best Practices für die Farbe, siehe BashFAQ # 37: http://mywiki.wooledge.org/BashFAQ/037 –

Antwort

5

Sammeln Sie die PIDs für die Hintergrundjobs; Verwenden Sie dann wait, um den Exit-Status jedes Elements zu erfassen, und verlassen Sie das erste Mal, wenn ein Polling-Polling in dieser Schleife ungleich Null ist.

install_pids=() 
for dir in ./projects/**/; do 
    (cd "$dir" && exec npm install) & install_pids+=($!) 
done 
for pid in "${install_pids[@]}"; do 
    wait "$pid" || exit 
done 

Die oben, während einfache, hat eine Einschränkung: Wenn ein Artikel spät in der Liste in der Liste früher ungleich Null vor Artikel verläßt, wird dies nicht bis zu diesem Punkt in der Liste beobachtet werden abgefragt . Zu Umgehen dieser Einschränkung, können Sie immer wieder durch die gesamte Liste durchlaufen:

install_pids=() 
for dir in ./projects/**/; do 
    (cd "$dir" && exec npm install) & install_pids+=($!) 
done 
while ((${#install_pids[@]})); do 
    for pid_idx in "${!install_pids[@]}"; do 
    pid=${install_pids[$pid_idx]} 
    if ! kill -0 "$pid" 2>/dev/null; then # kill -0 checks for process existance 
     # we know this pid has exited; retrieve its exit status 
     wait "$pid" || exit 
     unset "install_pids[$pid_idx]" 
    fi 
    done 
    sleep 1 # in bash, consider a shorter non-integer interval, ie. 0.2 
done 

Da jedoch diese Umfragen, es entsteht zusätzlichen Aufwand. Dies kann vermieden werden, indem SIGCHLD abgefangen und auf jobs -n verwiesen wird (um eine Liste von Jobs abzurufen, deren Status sich seit der vorherigen Abfrage geändert hat), wenn der Trap ausgelöst wird.

+0

Das kritische Detail hier ist, dass Sie auf jeden Prozess der Reihe nach warten müssen. Sie können nicht einfach warten, um auf alle zu warten. Sie erhalten den Rückgabecode nicht, wenn Sie das tun. –

+0

Dieser 'Ausgang' sofort. Das mag oder kann nicht das sein, was das OP will (obwohl ich nicht glaube, dass es in diesem Fall einen Unterschied machen wird). –

+0

@CharlesDuffy Damit wartet das Skript immer noch auf den Beendigungsstatus jedes Hintergrundjobs. Gibt es eine Möglichkeit, früh auszusteigen, wenn der erste einen Nicht-Null-Austrittsstatus hat? –

1

Bash ist nicht für die parallele Verarbeitung wie diese gemacht. Um zu erreichen, was Sie wollen, musste ich eine Funktionsbibliothek schreiben. Ich würde vorschlagen, wenn möglich eine Sprache zu suchen, die dafür besser geeignet ist.

Das Problem mit der Schleife durch die Pids, wie diese ...

#!/bin/bash 
pids=() 
f() { 
    sleep $1 
    echo "no good" 
    false 
} 

t() { 
    sleep $1 
    echo "good" 
    true 
} 

t 3 & 
pids+=$! 

f 1 & 
pids+=$! 

t 2 & 
pids+=$! 
for p in ${pids[@]}; do 
    wait $p || echo failed 
done 

Das Problem ist, dass „warten“ auf der ersten pid warten wird, und wenn die anderen pids beenden, bevor der erste Fall ist, werden Sie nicht den Exit-Code fangen. Der obige Code zeigt dieses Problem in bash v4.2.46. Der Befehl false sollte eine Ausgabe erzeugen, die niemals abgefangen wird.

+1

Siehe den Änderungsantrag zu meiner Antwort - das Ziel des OP * ist * nur mit Shell-Builtins lösbar. –

+0

BTW, ist das überhaupt eine Antwort? Ich sehe hier nichts, was eine vorgeschlagene Lösung zeigt. –

+0

Ich hätte Ihrer Antwort einen Kommentar hinzugefügt, aber S.O. verlangt eine 50 rep für das? Dein Schnitt ist großartig - die Kill-0-Nested-Schleife ist genial. – SaintHax