При наличии двух фоновых команд завершите оставшуюся при выходе

14

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

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

Если вторая команда завершается, кажется, что первая команда продолжает выполняться.

Как я могу изменить это так, что если одна из команд завершается, другая завершается?

Обратите внимание, что нам не нужно проверять уровни ошибок фоновых процессов, просто они вышли.

blah238
источник
Почему нет 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и второй процесс завершится первым. В любом случае можно увидеть, что это работает как хотелось бы.

John1024
источник
Это выглядит великолепно. К сожалению, эти команды не работают в моей среде 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процессов, как указано выше.
  • Посмотрите, какой фоновый процесс завершен. Если это не удалось, убей другого.
  • Если один процесс завершился неудачно, а другой был уничтожен, подождите, пока завершится второй, и сохраните его состояние в файле. Если первый процесс завершился успешно, дождитесь окончания второго.
  • Проверьте статусы двух процессов.
G-Man говорит: «Восстанови Монику»
источник
1

Для полноты вот что я в итоге использовал:

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

Это работает для меня на Git для Windows 2.5.3 64-bit. Старые версии могут не принять -nопцию на wait.

blah238
источник
1

В моей системе (Centos) waitнет, -nпоэтому я сделал это:

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

Это не ждет «либо», а ждет первого. Но все же это может помочь, если вы знаете, какой сервер будет остановлен первым.

Ондра Жижка
источник
Независимо от того , waitимеет -nопцию или нет , зависит от используемой оболочки, а не распределение Linux.
Кусалананда