Запустите несколько команд и убейте их как одну в bash

52

Я хочу запустить несколько команд (процессов) в одной оболочке. Все они имеют собственный непрерывный выход и не останавливаются. Запуск их в фоновом режиме перерывов Ctrl- C. Я хотел бы запустить их как единый процесс (подоболочка, может быть?), Чтобы иметь возможность остановить их все с помощью Ctrl- C.

Если быть точным, я хочу запустить модульные тесты с mocha(режим просмотра), запустить сервер и запустить некоторую предварительную обработку файлов (режим просмотра) и посмотреть вывод каждого из них в одном окне терминала. В основном я хочу избежать использования какого-либо бегуна задач.

Я могу реализовать это, запустив процессы в фоновом режиме ( &), но затем мне нужно поместить их на передний план, чтобы остановить их. Я хотел бы иметь процесс, чтобы обернуть их, и когда я остановлю процесс, он остановит своих «детей».

user1876909
источник
Должны ли они работать одновременно или один за другим?
Minix
Да, процессы должны запускаться одновременно, но мне нужно видеть вывод каждого из них.
user1876909

Ответы:

67

Для одновременного запуска команд вы можете использовать &разделитель команд.

~$ command1 & command2 & command3

Это запустится command1, затем запустится в фоновом режиме. То же самое с command2. Затем начинается command3нормально.

Вывод всех команд будет искажен вместе, но если это не проблема для вас, это было бы решением.

Если вы хотите позже просмотреть отдельный вывод, вы можете направить вывод каждой команды tee, что позволит вам указать файл для зеркального отображения вывода.

~$ command1 | tee 1.log & command2 | tee 2.log & command3 | tee 3.log

Вывод, вероятно, будет очень грязным. Чтобы противостоять этому, вы можете присвоить выводу каждой команды префикс, используя sed.

~$ echo 'Output of command 1' | sed -e 's/^/[Command1] /' 
[Command1] Output of command 1

Итак, если мы соберем все это вместе, мы получим:

~$ command1 | tee 1.log | sed -e 's/^/[Command1] /' & command2 | tee 2.log | sed -e 's/^/[Command2] /' & command3 | tee 3.log | sed -e 's/^/[Command3] /'
[Command1] Starting command1
[Command2] Starting command2
[Command1] Finished
[Command3] Starting command3

Это очень идеализированная версия того, что вы, вероятно, увидите. Но это лучшее, что я могу думать прямо сейчас.

Если вы хотите остановить все из них одновременно, вы можете использовать встроенную функцию trap.

~$ trap 'kill %1; kill %2' SIGINT
~$ command1 & command2 & command3

Это будет выполняться command1и command2в фоновом режиме, и command3на переднем плане, что позволяет убить его с помощью Ctrl+ C.

Когда вы убиваете последний процесс с Ctrl+ Cна kill %1; kill %2командах выполняются, потому что мы соединили их исполнение с приемом на ПРЕРЫВАЙТЕ СИГНАЛ, вещь посланного нажатия Ctrl+ C.

Они соответственно убивают 1-й и 2-й фоновый процесс (ваш command1и command2). Не забудьте удалить ловушку после того, как вы закончите с использованием команд trap - SIGINT.

Полный монстр команды:

~$ trap 'kill %1; kill %2' SIGINT
~$ command1 | tee 1.log | sed -e 's/^/[Command1] /' & command2 | tee 2.log | sed -e 's/^/[Command2] /' & command3 | tee 3.log | sed -e 's/^/[Command3] /'

Вы могли бы, конечно, взглянуть на экран . Это позволяет вам разделить вашу консоль на столько разных консолей, сколько вы хотите. Таким образом, вы можете контролировать все команды по отдельности, но в то же время.

Minix
источник
3
С нетерпением жду всей критики. :)
Minix
2
Спасибо. Он делает именно то, что я хотел (команда trap). Решение должно быть обернуто в сценарий для повторного использования, хотя.
user1876909
@ user1876909 Вот скелет. Специфика зависит от вас [1]. Рад, что смог помочь. [1] pastebin.com/jKhtwF40
Minix
3
это довольно умное решение, я узнал что-то новое, браво +1
mike-m
1
Это потрясающе. Мне нужно многое изучить и извлечь уроки из этого ответа.
cnnokes
20

Вы можете легко убить сразу несколько процессов, если вы организуете их (и только их) в одной группе процессов .

Linux предоставляет утилиту setsidдля запуска программы в новой группе процессов (даже в новом сеансе, но нас это не волнует). (Это выполнимо, но без него сложнееsetsid .)

Идентификатор группы процессов (PGID) группы процессов - это идентификатор процесса исходного родительского процесса в группе. Чтобы уничтожить все процессы в группе процессов, передайте отрицательное значение PGID killсистемному вызову или команде. PGID остается действительным, даже если оригинальный процесс с этим PID умирает (хотя это может немного сбивать с толку).

setsid sh -c 'command1 & command2 & command3' &
pgid=$!
echo "Background tasks are running in process group $pgid, kill with kill -TERM -$pgid"

Если вы запустите процессы в фоновом режиме из неинтерактивной оболочки, все они останутся в группе процессов оболочки. Только в интерактивных оболочках фоновые процессы выполняются в своей собственной группе процессов. Поэтому, если вы разветвляете команды из неинтерактивной оболочки, которая остается на переднем плане, Ctrl+ Cубьет их всех. Используйте waitвстроенную функцию, чтобы оболочка ожидала завершения всех команд.

sh -c 'command1 & command2 & command3 & wait'
# Press Ctrl+C to kill them all
Жиль "ТАК - перестань быть злым"
источник
2
недооцененный ответ (метод внизу). Просто понять и запомнить.
Николас Маршалл
14

Я удивлен, что это еще не здесь, но мой любимый способ сделать это - использовать подоболочки с фоновыми командами. Использование:

(command1 & command2 & command3)

Вывод виден из обоих, все команды bash и удобства все еще доступны, и один Ctrl-cубивает их всех. Я обычно использую это для одновременного запуска клиентского файлового сервера с живой отладкой и внутреннего сервера, поэтому я могу легко убить их обоих, когда захочу.

Как это работает, что command1и command2работает в фоновом режиме нового экземпляра подоболочки, и command3на переднем плане этого экземпляра, и подоболочка является то , что первым получает сигнал Разрушитель от Ctrl-cнажатия клавиши. Это очень похоже на запуск bash-скрипта в отношении того, кому принадлежит какой процесс и когда фоновые программы убиваются.

JV-DEV
источник
Если вы добавите waitокончательную команду (команда 3 в вашем примере), вы можете выполнить код очистки после того, как пользователь нажмет Ctrl-c.
dotnetCarpenter
0

Вы можете использовать точку с запятой ;или &&как это:

cmd1; cmd2   # Runs both the commands, even if the first one exits with a non-zero status.

cmd1 && cmd2 # Only runs the second command if the first one was successful.
serenesat
источник
Это не запускает команды одновременно.
Жиль "ТАК - перестань быть злым"
-2

Вы можете использовать pipe. Это запустит команды на обоих концах трубы одновременно. Вы должны быть в состоянии остановить все команды с помощью CTRL-Cтоже.

1st command | 2nd command | 3rd command

Если вы хотите запускать команды одну за другой, вы можете использовать метод @ serenesat.

Sree
источник
Это передает вывод первой команды второй (и т. Д.), Что здесь не подходит, поскольку вывод первой команды должен заканчиваться на терминале.
Жиль "ТАК - перестань быть злым"