Esegui più comandi e uccidili come uno in bash


52

Voglio eseguire più comandi (processi) su una singola shell. Tutti hanno un proprio output continuo e non si fermano. Eseguendoli in background si interrompe Ctrl- C. Vorrei eseguirli come un singolo processo (subshell, forse?) Per riuscire a fermarli tutti con Ctrl- C.

Per essere precisi, voglio eseguire unit test con mocha(modalità watch), eseguire il server ed eseguire una preelaborazione di file (modalità watch) e vedere l'output di ciascuno in una finestra terminale. Fondamentalmente voglio evitare di usare un task runner.

Posso realizzarlo eseguendo processi in background ( &), ma poi devo metterli in primo piano per fermarli. Mi piacerebbe avere un processo per avvolgerli e quando interrompo il processo si ferma i suoi "figli".


Dovrebbero correre contemporaneamente o uno dopo l'altro?
Minix,

Sì, i processi dovrebbero essere eseguiti contemporaneamente, ma devo vedere l'output di ciascuno.
user1876909

Risposte:


67

Per eseguire i comandi contemporaneamente è possibile utilizzare il &separatore dei comandi.

~$ command1 & command2 & command3

Verrà avviato command1, quindi verrà eseguito in background. Lo stesso con command2. Quindi inizia command3normalmente.

L'output di tutti i comandi verrà confuso, ma se ciò non fosse un problema per te, sarebbe la soluzione.

Se si desidera dare uno sguardo separato all'output in un secondo momento, è possibile reindirizzare l'output di ciascun comando tee, che consente di specificare un file su cui eseguire il mirroring dell'output.

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

L'output sarà probabilmente molto disordinato. Per contrastare ciò, è possibile assegnare un prefisso all'output di ogni comando sed.

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

Quindi se mettiamo tutto insieme otteniamo:

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

Questa è una versione altamente idealizzata di ciò che probabilmente vedrai. Ma è il migliore che mi viene in mente in questo momento.

Se vuoi fermarli tutti in una volta, puoi usare il build in trap.

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

Questo verrà eseguito command1e command2in background e command3in primo piano, che ti consente di ucciderlo con Ctrl+ C.

Quando si interrompe l'ultimo processo con Ctrl+ Ci kill %1; kill %2comandi vengono eseguiti, perché abbiamo collegato la loro esecuzione con la ricezione di un INTERupt SIGnal, la cosa inviata premendo Ctrl+ C.

Uccidono rispettivamente il primo e il secondo processo in background (tuo command1e command2). Non dimenticare di rimuovere la trappola, dopo aver finito di utilizzare i comandi trap - SIGINT.

Mostro completo di 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] /'

Ovviamente potresti dare un'occhiata allo schermo . Ti consente di dividere la tua console in tutte le console separate che desideri. Quindi puoi monitorare tutti i comandi separatamente, ma allo stesso tempo.


3
In attesa di tutte le critiche. :)
Minix,

2
Grazie. Fa esattamente quello che volevo (il comando trap). Tuttavia, la soluzione deve essere racchiusa in uno script per la riusabilità.
user1876909

@ user1876909 Ecco uno scheletro. I dettagli dipendono da te [1]. Sono contento di poterti aiutare. [1] pastebin.com/jKhtwF40
Minix,

3
questa è una soluzione piuttosto intelligente, ho imparato qualcosa di nuovo, bravo +1
mike-m,

1
Questo è fantastico Ho molto da approfondire e imparare da questa risposta.
ccnokes,

20

Puoi facilmente uccidere un sacco di processi contemporaneamente se organizzi di eseguirli (e solo loro) nello stesso gruppo di processi .

Linux fornisce l'utilità setsidper eseguire un programma in un nuovo gruppo di processi (anche in una nuova sessione, ma non ci interessa). (Questo è fattibile ma più complicato senza setsid.)

L'ID gruppo di processi (PGID) di un gruppo di processi è l'ID del processo genitore originale nel gruppo. Per terminare tutti i processi in un gruppo di processi, passare il negativo del PGID alla killchiamata o al comando di sistema. Il PGID rimane valido anche se il processo originale con questo PID muore (anche se può essere un po 'confuso).

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

Se si eseguono i processi in background da una shell non interattiva , rimarranno tutti nel gruppo di processi della shell. È solo nelle shell interattive che i processi in background vengono eseguiti nel proprio gruppo di processi. Pertanto, se esegui il fork dei comandi da una shell non interattiva che rimane in primo piano, Ctrl+ Cli ucciderà tutti. Utilizzare il waitcomando incorporato per fare in modo che la shell attenda la chiusura di tutti i comandi.

sh -c 'command1 & command2 & command3 & wait'
# Press Ctrl+C to kill them all

2
risposta non sottovalutata (il metodo in fondo). Semplice da capire e da memorizzare.
Nicolas Marshall,

14

Sono sorpreso che questo non sia ancora qui, ma il mio modo preferito di farlo è usare sottotitoli con comandi in background. Uso:

(command1 & command2 & command3)

L'output è visibile da entrambi, tutti i comandi e le comodità bash sono ancora disponibili e un singolo Ctrl-cli uccide tutti. Di solito lo uso per avviare contemporaneamente un file server client di debug live e un server back-end, quindi posso ucciderli entrambi facilmente quando voglio.

Il modo in cui funziona è quello command1e command2sono in esecuzione sullo sfondo di una nuova istanza della subshell, ed command3è in primo piano di tale istanza, e la subshell è ciò che per primo riceve il segnale di kill da un Ctrl-ctasto premuto. È molto simile all'esecuzione di uno script bash rispetto a chi possiede quale processo e quando i programmi in background vengono uccisi.


Se aggiungi waitcome comando finale (command3 nel tuo esempio), puoi eseguire il codice di pulizia dopo che l'utente ha premuto Ctrl-c.
dotnetCarpenter

0

Puoi usare il punto ;e virgola o in &&questo modo:

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.

Ciò non esegue i comandi contemporaneamente.
Gilles 'SO- smetti di essere malvagio'

-2

Puoi usare a pipe. Ciò avvierà i comandi su entrambe le estremità del tubo contemporaneamente. Dovresti essere in grado di interrompere anche tutti i comandi con CTRL-C.

1st command | 2nd command | 3rd command

Se vuoi eseguire i comandi uno dopo l'altro, puoi usare il metodo di @ serenesat.


Questo passa l'output del primo comando al secondo (e così via), che qui non è appropriato poiché l'output del primo comando dovrebbe finire sul terminale.
Gilles 'SO- smetti di essere malvagio'
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.