Execute vários comandos e mate-os como um no bash

52

Eu quero executar vários comandos (processos) em um único shell. Todos eles têm saída contínua própria e não param. Executá-los nas quebras de fundo Ctrl- C. Eu gostaria de executá-los como um único processo (subshell, talvez?) Para poder parar todos eles com Ctrl- C.

Para ser específico, quero executar testes de unidade com mocha(modo de observação), executar servidor e executar algum pré-processamento de arquivo (modo de observação) e ver a saída de cada um em uma janela do terminal. Basicamente, quero evitar o uso de um corredor de tarefas.

Eu posso perceber isso executando processos em segundo plano ( &), mas preciso colocá-los em primeiro plano para detê-los. Eu gostaria de ter um processo para envolvê-los e quando eu paro o processo, ele pára seus 'filhos'.

user1876909
fonte
Eles devem ser executados simultaneamente ou um após o outro?
Minix 20/05
Sim, os processos devem ser executados simultaneamente, mas eu preciso ver a saída de cada um.
User1876909

Respostas:

67

Para executar comandos simultaneamente, você pode usar o &separador de comandos.

~$ command1 & command2 & command3

Isso iniciará command1e depois o executará em segundo plano. O mesmo com command2. Então começa command3normalmente.

A saída de todos os comandos será ilegível, mas se isso não for um problema para você, essa seria a solução.

Se você quiser ter uma visão separada da saída posteriormente, poderá canalizar a saída de cada comando tee, o que permite especificar um arquivo para espelhar a saída.

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

A saída provavelmente será muito confusa. Para combater isso, você pode dar à saída de cada comando um prefixo usando sed.

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

Então, se juntarmos tudo isso, obtemos:

~$ 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 é uma versão altamente idealizada do que você provavelmente verá. Mas é o melhor que consigo pensar agora.

Se você deseja interromper todos eles de uma vez, pode usar a compilação trap.

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

Isto irá executar command1e command2no fundo e command3em primeiro plano, o que permite que você matá-lo com Ctrl+ C.

Quando você mata o último processo com Ctrl+, Cos kill %1; kill %2comandos são executados, porque conectamos a execução deles com a recepção de um INTerupt SIGnal, a coisa enviada pressionando Ctrl+ C.

Eles matam, respectivamente, o 1º e o 2º processo em segundo plano (seu command1e command2). Não se esqueça de remover a armadilha depois que você terminar de usar seus comandos trap - SIGINT.

Monstro completo de um 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] /'

Você poderia, é claro, dar uma olhada na tela . Permite dividir seu console em quantos consoles separados você desejar. Portanto, você pode monitorar todos os comandos separadamente, mas ao mesmo tempo.

Minix
fonte
3
Ansioso por todas as críticas. :)
Minix 20/05
2
Obrigado. Faz exatamente o que eu queria (o comando trap). A solução precisa ser agrupada em um script para reutilização.
User1876909
@ user1876909 Aqui está um esqueleto. Os detalhes são com você [1]. Ainda bem que pude ajudar. [1] pastebin.com/jKhtwF40
Minix
3
esta é uma solução bastante inteligente, iv aprendi algo novo, bravo +1 #
mike-m
11
Isso é incrível. Eu tenho muito a explorar e aprender com esta resposta.
Ccnokes 17/08/19
20

Você pode facilmente matar um monte de processos de uma só vez, se você deseja executá-los (e somente eles) no mesmo grupo de processos .

O Linux fornece o utilitário setsidpara executar um programa em um novo grupo de processos (em uma nova sessão, até, mas não nos importamos com isso). (Isso é factível, mas mais complicado sem setsid.)

O ID do grupo de processos (PGID) de um grupo de processos é o ID do processo pai original no grupo. Para eliminar todos os processos em um grupo de processos, passe o negativo do PGID para a killchamada ou comando do sistema. O PGID permanece válido mesmo que o processo original com este PID morra (embora possa ser um pouco confuso).

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

Se você executar os processos em segundo plano a partir de um shell não interativo , todos eles permanecerão no grupo de processos do shell. É apenas em shells interativos que os processos em segundo plano são executados em seu próprio grupo de processos. Portanto, se você bifurcar os comandos de um shell não interativo que permaneça em primeiro plano, o Ctrl+ Cmatará todos eles. Use o waitbuiltin para fazer o shell aguardar a saída de todos os comandos.

sh -c 'command1 & command2 & command3 & wait'
# Press Ctrl+C to kill them all
Gilles 'SO- parar de ser mau'
fonte
2
resposta discreta (o método na parte inferior). Simples de entender e memorizar.
Nicolas Marshall
14

Estou surpreso que isso ainda não esteja aqui, mas minha maneira favorita de fazer isso é usar subshells com comandos em segundo plano. Uso:

(command1 & command2 & command3)

A saída é visível em ambos, todos os comandos e conveniências do bash ainda estão disponíveis, e um único Ctrl-cmata todos eles. Eu costumo usar isso para iniciar um servidor de arquivos cliente de depuração ao vivo e um servidor back-end ao mesmo tempo, para que eu possa matá-los facilmente quando quiser.

A maneira como isso funciona é isso command1e command2está sendo executada em segundo plano de uma nova instância de subshell, e command3está em primeiro plano nessa instância, e o subshell é o que recebe primeiro o sinal de Ctrl-cinterrupção de um pressionamento de tecla. É como executar um script bash com relação a quem possui qual processo e quando os programas em segundo plano são mortos.

jv-dev
fonte
Se você adicionar waitcomo o comando final (comando3 no seu exemplo), poderá executar o código de limpeza após o usuário pressionar Ctrl-c.
DotnetCarpenter
0

Você pode usar o ponto- ;e- vírgula ou &&assim:

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
fonte
Isso não executa os comandos simultaneamente.
Gilles 'SO- stop be evil'
-2

Você pode usar a pipe. Isso iniciará os comandos nas duas extremidades do tubo ao mesmo tempo. Você deve poder parar todos os comandos com o CTRL-Ctambém.

1st command | 2nd command | 3rd command

Se você deseja executar os comandos um após o outro, pode usar o método do @ serenesat.

Sree
fonte
Isso passa a saída do primeiro comando para o segundo (e assim por diante), o que não é apropriado aqui, pois a saída do primeiro comando deve terminar no terminal.
Gilles 'SO- stop be evil'