Ejecuta múltiples comandos y mátalos como uno en bash

52

Quiero ejecutar múltiples comandos (procesos) en un solo shell. Todos tienen salida continua propia y no se detienen. Correr en los saltos de fondo Ctrl- C. Me gustaría ejecutarlos como un solo proceso (¿subshell, tal vez?) Para poder detenerlos a todos con Ctrl- C.

Para ser específicos, quiero ejecutar pruebas unitarias con mocha(modo de observación), ejecutar el servidor y ejecutar algún preprocesamiento de archivos (modo de observación) y ver la salida de cada uno en una ventana de terminal. Básicamente quiero evitar usar algún corredor de tareas.

Puedo darme cuenta ejecutando procesos en segundo plano ( &), pero luego tengo que ponerlos en primer plano para detenerlos. Me gustaría tener un proceso para envolverlos y cuando detengo el proceso, detiene a sus 'hijos'.

usuario1876909
fuente
¿Deben correr simultáneamente o uno tras otro?
Minix
Sí, los procesos deberían ejecutarse simultáneamente, pero necesito ver el resultado de cada uno.
user1876909

Respuestas:

67

Para ejecutar comandos simultáneamente, puede usar el &separador de comandos.

~$ command1 & command2 & command3

Esto comenzará command1, luego lo ejecuta en segundo plano. Lo mismo con command2. Entonces comienza command3normalmente.

La salida de todos los comandos se confundirá, pero si eso no es un problema para usted, esa sería la solución.

Si desea ver más adelante la salida por separado, puede canalizar la salida de cada comando tee, lo que le permite especificar un archivo para reflejar la salida.

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

La salida probablemente será muy desordenada. Para contrarrestar eso, podría darle un prefijo a la salida de cada comando sed.

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

Entonces, si juntamos todo eso, obtenemos:

~$ 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

Esta es una versión altamente idealizada de lo que probablemente va a ver. Pero es lo mejor que puedo pensar en este momento.

Si desea detenerlos todos a la vez, puede usar la compilación trap.

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

Esto se ejecutará command1y command2en segundo plano y command3en primer plano, lo que le permite matarlo con Ctrl+ C.

Cuando matas el último proceso con Ctrl+, Clos kill %1; kill %2comandos se ejecutan, porque conectamos su ejecución con la recepción de un INTERupt SIGnal, lo que se envía presionando Ctrl+ C.

Matan, respectivamente, el primer y segundo proceso de fondo (tu command1y command2). No olvides quitar la trampa, una vez que hayas terminado de usar tus comandos trap - SIGINT.

Monstruo completo de un comando:

~$ 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] /'

Podría, por supuesto, echar un vistazo a la pantalla . Le permite dividir su consola en tantas consolas separadas como desee. Entonces puede monitorear todos los comandos por separado, pero al mismo tiempo.

Minix
fuente
3
Espero con ansias todas las críticas. :)
Minix
2
Gracias. Hace exactamente lo que quería (el comando trap). Sin embargo, la solución debe estar envuelta en un script para su reutilización.
user1876909
@ user1876909 Aquí hay un esqueleto. Los detalles dependen de usted [1]. Me alegro de poder ayudar. [1] pastebin.com/jKhtwF40
Minix
3
esta es una solución bastante inteligente, iv aprendí algo nuevo, bravo +1
mike-m
1
Esto es increíble. Tengo mucho que investigar y aprender de esta respuesta.
ccnokes
20

Puede eliminar fácilmente un montón de procesos a la vez si decide ejecutarlos (y solo ellos) en el mismo grupo de procesos .

Linux proporciona la utilidad setsidpara ejecutar un programa en un nuevo grupo de procesos (incluso en una nueva sesión, pero eso no nos importa). (Esto es factible pero más complicado sin él setsid).

La ID de grupo de proceso (PGID) de un grupo de proceso es la ID de proceso del proceso padre original en el grupo. Para eliminar todos los procesos en un grupo de procesos, pase el negativo del PGID a la killllamada o comando del sistema. El PGID sigue siendo válido incluso si el proceso original con este PID muere (aunque puede ser un poco confuso).

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

Si ejecuta los procesos en segundo plano desde un shell no interactivo , todos permanecerán en el grupo de procesos del shell. Solo en los shells interactivos los procesos en segundo plano se ejecutan en su propio grupo de procesos. Por lo tanto, si bifurca los comandos de un shell no interactivo que permanece en primer plano, Ctrl+ Clos matará a todos. Use el waitincorporado para hacer que el shell espere a que salgan todos los comandos.

sh -c 'command1 & command2 & command3 & wait'
# Press Ctrl+C to kill them all
Gilles 'SO- deja de ser malvado'
fuente
2
respuesta no atenuada (el método en la parte inferior). Simple de entender y memorizar.
Nicolas Marshall
14

Me sorprende que esto aún no esté aquí, pero mi forma favorita de hacerlo es usar subcapas con comandos de fondo. Uso:

(command1 & command2 & command3)

La salida es visible desde ambos, todos los comandos bash y las comodidades aún están disponibles, y un solo los Ctrl-cmata a todos. Por lo general, uso esto para iniciar un servidor de archivos de cliente de depuración en vivo y un servidor de fondo al mismo tiempo, por lo que puedo eliminarlos fácilmente cuando quiera.

La forma en que esto funciona es eso command1y se command2está ejecutando en el fondo de una nueva instancia de subshell, y command3está en primer plano de esa instancia, y la subshell es lo que primero recibe la señal de Ctrl-capagado de una pulsación de tecla. Es muy parecido a ejecutar un script bash con respecto a quién posee qué proceso y cuándo se matan los programas en segundo plano.

jv-dev
fuente
Si agrega waitcomo el comando final (comando3 en su ejemplo), puede ejecutar el código de limpieza después de que el usuario presione Ctrl-c.
dotnetCarpenter
0

Puedes usar el punto ;y coma o &&así:

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
fuente
Eso no ejecuta los comandos al mismo tiempo.
Gilles 'SO- deja de ser malvado'
-2

Puedes usar a pipe. Esto iniciará los comandos en ambos extremos de la tubería al mismo tiempo. Usted debe ser capaz de detener todos los comandos con el CTRL-Ctambién.

1st command | 2nd command | 3rd command

Si desea ejecutar los comandos uno tras otro, puede usar el método de @ serenesat.

Sree
fuente
Esto pasa la salida del primer comando al segundo (y así sucesivamente), lo que no es apropiado aquí ya que se supone que la salida del primer comando termina en el terminal.
Gilles 'SO- deja de ser malvado'