Давши дві фонові команди, закінчіть решту, коли будь-яка програма виходить


14

У мене простий скрипт bash, який запускає два сервери:

#!/bin/bash
(cd ./frontend && gulp serve) & (cd ./backend && gulp serve --verbose)

Якщо друга команда завершиться, здається, що перша команда продовжує працювати.

Як я можу змінити це так, що якщо будь-яка команда закінчується, інша припиняється?

Зауважте, що нам не потрібно перевіряти рівні помилок фонових процесів, лише чи не відбулися вони.


Чому ні gulp ./fronend/serve && gulp ./backend/serve --verbose?
heemayl

serveце аргумент, а не файл, тому потрібно встановити поточний каталог.
blah238

1
Також це тривалі процеси, які потрібно запускати одночасно, вибачте, якщо це було не ясно.
blah238

Відповіді:


22

Це запускає обидва процеси, чекає першого, який закінчиться, а потім вбиває іншого:

#!/bin/bash
{ cd ./frontend && gulp serve; } &
{ cd ./backend && gulp serve --verbose; } &
wait -n
pkill -P $$

Як це працює

  1. Початок:

    { cd ./frontend && gulp serve; } &
    { cd ./backend && gulp serve --verbose; } &

    Вищеописані дві команди запускають обидва процеси у фоновому режимі.

  2. Зачекайте

    wait -n

    Це очікує припинення будь-якого фонового завдання.

    Через -nопцію для цього потрібен bash 4.3 або вище.

  3. Убити

    pkill -P $$

    Це вбиває будь-яку роботу, для якої поточний процес є батьківським. Іншими словами, це вбиває будь-який фоновий процес, який ще триває.

    Якщо у вашій системі немає pkill, спробуйте замінити цей рядок на:

    kill 0

    що також вбиває поточну групу процесу .

Легко перевіряється приклад

Змінивши скрипт, ми можемо перевірити його навіть без gulpвстановленого:

$ cat script.sh 
#!/bin/bash
{ sleep $1; echo one;  } &
{ sleep $2; echo two;  } &
wait -n
pkill -P $$
echo done

Вищеописаний сценарій можна запустити як, bash script.sh 1 3і перший процес закінчується першим. Крім того, можна запустити його як bash script.sh 3 1і другий процес закінчиться перший. В будь-якому випадку видно, що це працює за бажанням.


Це виглядає чудово. На жаль, ці команди не працюють у моєму середовищі bash (msysgit). Це моє погано, якщо я цього не вказував. Я все ж спробую це на справжньому вікні Linux.
blah238

(1) Не всі версії bashпідтримують -nопцію для waitкоманди. (2) Я погоджуюсь на 100% з першим реченням - ваше рішення запускає два процеси, чекає, коли перший закінчиться, а потім вбиває другий. Але питання говорить: "... якщо будь-яка команда помилиться , інша припиняється?" Я вважаю, що ваше рішення - це не те, чого хоче ОП. (3) Чому ви змінили (…) &до { …; } &? В &силах списку (група команд) , щоб запустити в субоболочке в будь-якому випадку. ІМХО, ви додали символів і, можливо, внесли плутанину (мені довелося подивитися на це двічі, щоб зрозуміти це) без користі.
G-Man каже: "Відновіть Моніку"

1
Джон вірно, це веб-сервери, які, як правило, обидва продовжують працювати, якщо не сталася помилка або не подано сигнал про припинення. Тому я не думаю, що нам потрібно перевіряти рівень помилок кожного процесу, просто чи він все ще працює.
blah238

2
pkillнедоступний для мене, але, kill 0здається, має той же ефект. Також я оновив своє середовище Git для Windows і, схоже, wait -nзараз працює, тому приймаю цю відповідь.
blah238

1
Цікаво. Хоча я розумію , що така поведінка документовано для деяких версій про kill. документація в моїй системі не згадує про це. Однак kill 0працює все одно. Гарна знахідка!
John1024,

1

Це хитро. Ось що я створив; можливо можливо спростити / упорядкувати його:

#!/bin/sh

pid1file=$(mktemp)
pid2file=$(mktemp)
stat1file=$(mktemp)
stat2file=$(mktemp)

while true; do sleep 42; done &
main_sleeper=$!

(cd frontend && gulp serve           & echo "$!" > "$pid1file";
    wait "$!" 2> /dev/null; echo "$?" > "$stat1file"; kill "$main_sleeper" 2> /dev/null) &
(cd backend  && gulp serve --verbose & echo "$!" > "$pid2file";
    wait "$!" 2> /dev/null; echo "$?" > "$stat2file"; kill "$main_sleeper" 2> /dev/null) &
sleep 1
wait "$main_sleeper" 2> /dev/null

if stat1=$(<"$stat1file")  &&  [ "$stat1" != "" ]  &&  [ "$stat1" != 0 ]
then
        echo "First process failed ..."
        if pid2=$(<"$pid2file")  &&  [ "$pid2" != "" ]
        then
                echo "... killing second process."
                kill "$pid2" 2> /dev/null
        fi
fi
if [ "$stat1" = "" ]  &&  \
   stat2=$(<"$stat2file")  &&  [ "$stat2" != "" ]  &&  [ "$stat2" != 0 ]
then
        echo "Second process failed ..."
        if pid1=$(<"$pid1file")  &&  [ "$pid1" != "" ]
        then
                echo "... killing first process."
                kill "$pid1" 2> /dev/null
        fi
fi

wait
if stat1=$(<"$stat1file")
then
        echo "Process 1 terminated with status $stat1."
else
        echo "Problem getting status of process 1."
fi
if stat2=$(<"$stat2file")
then
        echo "Process 2 terminated with status $stat2."
else
        echo "Problem getting status of process 2."
fi
  • По-перше, запустіть процес ( while true; do sleep 42; done &), який спає / призупиняється назавжди. Якщо ви впевнені, що ваші дві команди припиняться протягом певного часу (наприклад, за годину), ви можете змінити це на один сон, який перевищить цей (наприклад, sleep 3600). Потім ви можете змінити таку логіку, щоб використовувати це як тайм-аут; тобто вбийте процеси, якщо вони все ще працюють через стільки часу. (Зауважте, що вищезазначений сценарій цього не робить.)
  • Запустіть два асинхронні (паралельні фонові) процеси.
    • Вам не потрібно ./для cd.
    • command & echo "$!" > somewhere; wait "$!" - хитра конструкція, яка запускає процес асинхронно, фіксує його PID, а потім чекає його; що робить його начебто переднього (синхронного) процесу. Але це відбувається в (…)списку, який знаходиться у фоновому режимі в повному обсязі, тому gulpпроцеси протікають асинхронно.
    • Після завершення будь-якого з gulpпроцесів запишіть його статус у тимчасовий файл і вкажіть процес "вічно сплячого".
  • sleep 1 для захисту від перегонових умов, коли перший фоновий процес гине, перш ніж другий отримує можливість записати свій PID у файл.
  • Зачекайте, коли процес "вічно спить" завершиться. Це відбувається після завершення будь- якого з gulpпроцесів, як зазначено вище.
  • Подивіться, який фоновий процес завершився. Якщо це не вдалося, вбийте іншого.
  • Якщо один процес не вдався, а ми вбили іншого, дочекайтеся завершення другого і збережіть його статус у файлі. Якщо перший процес закінчився успішно, дочекайтеся закінчення другого.
  • Перевірте статуси двох процесів.

1

Для повноти ось що я в кінцевому підсумку використав:

#!/bin/bash
(cd frontend && gulp serve) &
(cd backend && gulp serve --verbose) &
wait -n
kill 0

Це працює для мене на 64-розрядному Git для Windows 2.5.3. Старіші версії можуть не приймати цю -nопцію wait.


1

У моїй системі (Centos) waitнемає, -nтому я це зробив:

{ sleep 3; echo one;  } &
FOO=$!
{ sleep 6; echo two;  } &
wait $FOO
pkill -P $$

Це не чекає "жодного", а чекає першого. Але все-таки це може допомогти, якщо ви знаєте, який сервер буде зупинений першим.


Чи waitє -nопція чи ні, залежить від оболонки, яку ви використовуєте, а не дистрибутива Linux.
Кусалаланда
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.