Come si eseguono più programmi in parallelo da uno script bash?


245

Sto cercando di scrivere a file .sh che esegue molti programmi contemporaneamente

Ci ho provato

prog1 
prog2

Ma quello esegue prog1 quindi attende fino a quando prog1 termina e quindi inizia prog2 ...

Quindi, come posso eseguirli in parallelo?

Risposte:


216
prog1 &
prog2 &

49
Non dimenticare il wait! Sì, in bash puoi aspettare i processi figlio dello script.
Dummy00001,

5
Un'altra opzione è usare nohupper evitare che il programma venga ucciso quando la shell si blocca.
Philipp,

@liang: Sì, funzionerà anche con tre o più programmi.
psmears,

302

Che ne dite di:

prog1 & prog2 && fg

Questo sarà:

  1. Inizio prog1.
  2. Invialo in background, ma continua a stampare il suo output.
  3. Inizia prog2e tienilo in primo piano , così puoi chiuderlo con ctrl-c.
  4. Quando si chiude prog2, si tornerà a prog1's in primo piano , in modo da poter anche vicino con ctrl-c.

9
C'è un modo semplice per terminare prog1quando prog2termina? Pensa a node srv.js & cucumberjs
JP

20
Ho appena provato questo e non ha funzionato come previsto per me. Tuttavia, una leggera modifica ha funzionato: prog1 & prog2 ; fg era per eseguire più tunnel ssh contemporaneamente. Spero che questo aiuti qualcuno.
jnadro52,

2
@ jnadro52 la tua soluzione ha l'effetto che se prog2non si esegue immediatamente, tornerai ad avere prog1in primo piano. Se questo è desiderabile, allora va bene.
Ory Band,

3
Sulla shell SSH Se si esegue un comando come questo, sarà complicato uccidere prog1. Ctrl-c non ha funzionato per me. Anche uccidere l'intero terminale ha lasciato prog1 in esecuzione.
mercury0114,

14
@ jnadro52 Un modo per terminare entrambi i processi contemporaneamente è prog1 & prog2 && kill $!.
zaboco,

79

Puoi usare wait:

some_command &
P1=$!
other_command &
P2=$!
wait $P1 $P2

Assegna i PID del programma in background alle variabili ( $!è l'ultimo PID del processo avviato), quindi il waitcomando li attende. È bello perché se uccidi lo script, uccide anche i processi!


4
Nella mia esperienza , uccidere l'attesa non uccide anche gli altri processi.
Quinn Comendant,

1
Se sto iniziando i processi in background in un ciclo, come posso attendere il completamento di tutti i processi in background prima di andare avanti con l'esecuzione del prossimo set di comandi. #!/usr/bin/env bash ARRAY='cat bat rat' for ARR in $ARRAY do ./run_script1 $ARR & done P1=$! wait $P1 echo "INFO: Execution of all background processes in the for loop has completed.."
Yash,

@Yash Penso che sia possibile salvare gli ID di processo in un array, quindi chiamare attendere sull'array. Penso che devi usare ${}per interpolarlo in un elenco di stringhe o simili.
trusktr,

la risposta migliore, e per me uccidere la sceneggiatura uccide anche i processi! macOS Catalina, console zsh
Michael Klishevich

67

Con GNU Parallel http://www.gnu.org/software/parallel/ è facile come:

(echo prog1; echo prog2) | parallel

O se preferisci:

parallel ::: prog1 prog2

Per saperne di più:


4
Vale la pena notare che esistono diverse versioni di parallelcon sintassi diversa. Ad esempio, sui derivati ​​Debian il moreutilspacchetto contiene un comando diverso chiamato parallelche si comporta in modo abbastanza diverso.
Joel Cross,

4
è parallelmeglio dell'uso &?
Optimus Prime,

2
@OptimusPrime Dipende davvero. GNU Parallel introduce un certo sovraccarico, ma in cambio ti dà molto più controllo sui lavori in esecuzione e sull'output. Se due lavori vengono stampati contemporaneamente, GNU Parallel si assicurerà che l'output non sia miscelato.
Ole Tange,

1
@OptimusPrime parallelè meglio quando ci sono più lavori che core, nel qual caso &eseguiresti più di un lavoro per core alla volta. (cfr. principio pigeonhole )
Geremia,

2
@OleTange "La tua riga di comando ti adorerà per questo. " Anche io. ☺
Geremia,

55

Se vuoi essere in grado di eseguire facilmente e uccidere più processi con ctrl-c, questo è il mio metodo preferito: spawn più processi in background in una (…)subshell e trap SIGINTda eseguire kill 0, che ucciderà tutto ciò che è generato nel gruppo subshell:

(trap 'kill 0' SIGINT; prog1 & prog2 & prog3)

Puoi avere strutture di esecuzione del processo complesse e tutto si chiuderà con un singolo ctrl-c(assicurati solo che l'ultimo processo sia eseguito in primo piano, cioè non includere un &after prog1.3):

(trap 'kill 0' SIGINT; prog1.1 && prog1.2 & (prog2.1 | prog2.2 || prog2.3) & prog1.3)

Questa è di gran lunga la risposta migliore.
Nic,

10

xargs -P <n>ti permette di eseguire <n>comandi in parallelo.

Mentre -P sia un'opzione non standard, entrambe le implementazioni GNU (Linux) e macOS / BSD la supportano.

Il seguente esempio:

  • corre al massimo 3 comandi in parallelo alla volta,
  • con comandi aggiuntivi che iniziano solo quando termina un processo precedentemente avviato.
time xargs -P 3 -I {} sh -c 'eval "$1"' - {} <<'EOF'
sleep 1; echo 1
sleep 2; echo 2
sleep 3; echo 3
echo 4
EOF

L'output è simile al seguente:

1   # output from 1st command 
4   # output from *last* command, which started as soon as the count dropped below 3
2   # output from 2nd command
3   # output from 3rd command

real    0m3.012s
user    0m0.011s
sys 0m0.008s

Il tempismo mostra che i comandi sono stati eseguiti in parallelo (l'ultimo comando è stato lanciato solo dopo che il primo dei 3 originali era terminato, ma eseguito molto rapidamente).

Il xargscomando stesso non verrà restituito fino al termine di tutti i comandi, ma è possibile eseguirlo in background terminandolo con l'operatore di controllo &e quindi utilizzando il waitcomando incorporato per attendere il completamento dell'intero xargscomando.

{
  xargs -P 3 -I {} sh -c 'eval "$1"' - {} <<'EOF'
sleep 1; echo 1
sleep 2; echo 2
sleep 3; echo 3
echo 4
EOF
} &

# Script execution continues here while `xargs` is running 
# in the background.
echo "Waiting for commands to finish..."

# Wait for `xargs` to finish, via special variable $!, which contains
# the PID of the most recently started background process.
wait $!

Nota:

  • BSD / macOS xargsrichiede di specificare il conteggio dei comandi da eseguire in parallelo in modo esplicito , mentre GNU xargsconsente di specificare -P 0l'esecuzione del maggior numero possibile in parallelo.

  • L'output dei processi eseguiti in parallelo arriva man mano che viene generato , quindi verrà intercalato in modo imprevedibile .

    • GNU parallel, come menzionato nella risposta di Ole (non viene fornito di serie con la maggior parte delle piattaforme), serializza (raggruppa) l'output in base al processo e offre molte altre funzionalità avanzate.

9
#!/bin/bash
prog1 & 2> .errorprog1.log; prog2 & 2> .errorprog2.log

Reindirizzare gli errori a registri separati.


13
Devi mettere le e commerciali dopo i reindirizzamenti ed escludere il punto e virgola (la e commerciale svolgerà anche la funzione di un separatore di comandi):prog1 2> .errorprog1.log & prog2 2> .errorprog2.log &
Pausa fino a ulteriore avviso.

il punto e virgola eseguono entrambi i comandi, puoi testare de bash per vederlo funzionare bene;) Esempio: pwd & 2> .errorprog1.log; echo "wop" & 2> .errorprog2.log quando metti e metti il ​​programma in background ed esegui immediatamente il comando successivo.
Fermin

2
Non funziona: gli errori non vengono reindirizzati al file. Prova con: ls notthere1 & 2> .errorprog1.log; ls notthere2 & 2>.errorprog2.log. Gli errori vanno alla console ed entrambi i file di errore sono vuoti. Come dice @Dennis Williamson, &è un separatore, ;quindi, (a) deve andare alla fine del comando (dopo qualsiasi reindirizzamento), e (b) non è necessario ;affatto :-)
psmears

8

C'è un programma molto utile che chiama nohup.

     nohup - run a command immune to hangups, with output to a non-tty

4
nohupdi per sé non esegue nulla in background e l'utilizzo nohupnon è un requisito o un prerequisito per l'esecuzione di attività in background. Sono spesso utili insieme ma, come tali, questo non risponde alla domanda.
triplo

8

Ecco una funzione che uso per eseguire al massimo n processo in parallelo (n = 4 nell'esempio):

max_children=4

function parallel {
  local time1=$(date +"%H:%M:%S")
  local time2=""

  # for the sake of the example, I'm using $2 as a description, you may be interested in other description
  echo "starting $2 ($time1)..."
  "$@" && time2=$(date +"%H:%M:%S") && echo "finishing $2 ($time1 -- $time2)..." &

  local my_pid=$$
  local children=$(ps -eo ppid | grep -w $my_pid | wc -w)
  children=$((children-1))
  if [[ $children -ge $max_children ]]; then
    wait -n
  fi
}

parallel sleep 5
parallel sleep 6
parallel sleep 7
parallel sleep 8
parallel sleep 9
wait

Se max_children è impostato sul numero di core, questa funzione tenterà di evitare i core inattivi.


1
Bello frammento, ma non riesco a trovare la spiegazione di "wait -n" sotto il mio bash, dice che è un'opzione non valida. refuso o mi sono perso qualcosa?
Emmanuel Devaux,

1
@EmmanuelDevaux: wait -nrichiede bash4.3+ e modifica la logica in attesa che uno qualsiasi dei processi specificati / impliciti venga interrotto.
mklement0

cosa succede se una delle attività fallisce, quindi voglio terminare gli script?
52coder

@ 52coder puoi regolare la funzione per catturare un figlio fallito, qualcosa del tipo: "$ @" && time2 = $ (data + "% H:% M:% S") && echo "finendo $ 2 ($ time1 - $ time2 ) ... "|| errore = 1 &. Quindi verifica l'errore nella parte "if" e
annulla

7

Puoi provare i pps . ppss è piuttosto potente: puoi persino creare un mini-cluster. xargs -P può anche essere utile se hai una serie di elaborazioni parallele imbarazzanti da fare.


7

Recentemente ho avuto una situazione simile in cui avevo bisogno di eseguire più programmi contemporaneamente, reindirizzare i loro output su file di registro separati e aspettare che finissero e ho finito con qualcosa del genere:

#!/bin/bash

# Add the full path processes to run to the array
PROCESSES_TO_RUN=("/home/joao/Code/test/prog_1/prog1" \
                  "/home/joao/Code/test/prog_2/prog2")
# You can keep adding processes to the array...

for i in ${PROCESSES_TO_RUN[@]}; do
    ${i%/*}/./${i##*/} > ${i}.log 2>&1 &
    # ${i%/*} -> Get folder name until the /
    # ${i##*/} -> Get the filename after the /
done

# Wait for the processes to finish
wait

Fonte: http://joaoperibeiro.com/execute-multiple-programs-and-redirect-their-outputs-linux/


4

Responsabile di spawn dei processi

Certo, tecnicamente si tratta di processi, e questo programma dovrebbe davvero essere chiamato un gestore di spawn dei processi, ma questo è dovuto solo al modo in cui BASH funziona quando si biforca usando la e commerciale, usa la chiamata di sistema fork () o forse clone () che clona in uno spazio di memoria separato, piuttosto che qualcosa come pthread_create () che condividerebbe la memoria. Se BASH supportasse quest'ultimo, ogni "sequenza di esecuzione" funzionerebbe esattamente allo stesso modo e potrebbe essere definita come thread tradizionale ottenendo un footprint di memoria più efficiente. Funzionalmente tuttavia funziona allo stesso modo, anche se un po 'più difficile poiché le variabili GLOBAL non sono disponibili in ciascun clone di lavoro, quindi l'uso del file di comunicazione tra processi e il semaforo rudimentale del gregge per gestire le sezioni critiche. Il fork di BASH ovviamente è la risposta di base qui, ma mi sento come se la gente lo sapesse, ma sta davvero cercando di gestire ciò che viene generato piuttosto che semplicemente fork e dimenticarlo. Questo dimostra un modo per gestire fino a 200 istanze di processi biforcati che accedono tutti a una singola risorsa. Chiaramente questo è eccessivo, ma mi è piaciuto scriverlo, quindi ho continuato. Aumenta le dimensioni del tuo terminale di conseguenza. Spero che lo trovi utile

ME=$(basename $0)
IPC="/tmp/$ME.ipc"      #interprocess communication file (global thread accounting stats)
DBG=/tmp/$ME.log
echo 0 > $IPC           #initalize counter
F1=thread
SPAWNED=0
COMPLETE=0
SPAWN=1000              #number of jobs to process
SPEEDFACTOR=1           #dynamically compensates for execution time
THREADLIMIT=50          #maximum concurrent threads
TPS=1                   #threads per second delay
THREADCOUNT=0           #number of running threads
SCALE="scale=5"         #controls bc's precision
START=$(date +%s)       #whence we began
MAXTHREADDUR=6         #maximum thread life span - demo mode

LOWER=$[$THREADLIMIT*100*90/10000]   #90% worker utilization threshold
UPPER=$[$THREADLIMIT*100*95/10000]   #95% worker utilization threshold
DELTA=10                             #initial percent speed change

threadspeed()        #dynamically adjust spawn rate based on worker utilization
{
   #vaguely assumes thread execution average will be consistent
   THREADCOUNT=$(threadcount)
   if [ $THREADCOUNT -ge $LOWER ] && [ $THREADCOUNT -le $UPPER ] ;then
      echo SPEED HOLD >> $DBG
      return
   elif [ $THREADCOUNT -lt $LOWER ] ;then
      #if maxthread is free speed up
      SPEEDFACTOR=$(echo "$SCALE;$SPEEDFACTOR*(1-($DELTA/100))"|bc)
      echo SPEED UP $DELTA%>> $DBG
   elif [ $THREADCOUNT -gt $UPPER ];then
      #if maxthread is active then slow down
      SPEEDFACTOR=$(echo "$SCALE;$SPEEDFACTOR*(1+($DELTA/100))"|bc)
      DELTA=1                            #begin fine grain control
      echo SLOW DOWN $DELTA%>> $DBG
   fi

   echo SPEEDFACTOR $SPEEDFACTOR >> $DBG

   #average thread duration   (total elapsed time / number of threads completed)
   #if threads completed is zero (less than 100), default to maxdelay/2  maxthreads

   COMPLETE=$(cat $IPC)

   if [ -z $COMPLETE ];then
      echo BAD IPC READ ============================================== >> $DBG
      return
   fi

   #echo Threads COMPLETE $COMPLETE >> $DBG
   if [ $COMPLETE -lt 100 ];then
      AVGTHREAD=$(echo "$SCALE;$MAXTHREADDUR/2"|bc)
   else
      ELAPSED=$[$(date +%s)-$START]
      #echo Elapsed Time $ELAPSED >> $DBG
      AVGTHREAD=$(echo "$SCALE;$ELAPSED/$COMPLETE*$THREADLIMIT"|bc)
   fi
   echo AVGTHREAD Duration is $AVGTHREAD >> $DBG

   #calculate timing to achieve spawning each workers fast enough
   # to utilize threadlimit - average time it takes to complete one thread / max number of threads
   TPS=$(echo "$SCALE;($AVGTHREAD/$THREADLIMIT)*$SPEEDFACTOR"|bc)
   #TPS=$(echo "$SCALE;$AVGTHREAD/$THREADLIMIT"|bc)  # maintains pretty good
   #echo TPS $TPS >> $DBG

}
function plot()
{
   echo -en \\033[${2}\;${1}H

   if [ -n "$3" ];then
         if [[ $4 = "good" ]];then
            echo -en "\\033[1;32m"
         elif [[ $4 = "warn" ]];then
            echo -en "\\033[1;33m"
         elif [[ $4 = "fail" ]];then
            echo -en "\\033[1;31m"
         elif [[ $4 = "crit" ]];then
            echo -en "\\033[1;31;4m"
         fi
   fi
      echo -n "$3"
      echo -en "\\033[0;39m"
}

trackthread()   #displays thread status
{
   WORKERID=$1
   THREADID=$2
   ACTION=$3    #setactive | setfree | update
   AGE=$4

   TS=$(date +%s)

   COL=$[(($WORKERID-1)/50)*40]
   ROW=$[(($WORKERID-1)%50)+1]

   case $ACTION in
      "setactive" )
         touch /tmp/$ME.$F1$WORKERID  #redundant - see main loop
         #echo created file $ME.$F1$WORKERID >> $DBG
         plot $COL $ROW "Worker$WORKERID: ACTIVE-TID:$THREADID INIT    " good
         ;;
      "update" )
         plot $COL $ROW "Worker$WORKERID: ACTIVE-TID:$THREADID AGE:$AGE" warn
         ;;
      "setfree" )
         plot $COL $ROW "Worker$WORKERID: FREE                         " fail
         rm /tmp/$ME.$F1$WORKERID
         ;;
      * )

      ;;
   esac
}

getfreeworkerid()
{
   for i in $(seq 1 $[$THREADLIMIT+1])
   do
      if [ ! -e /tmp/$ME.$F1$i ];then
         #echo "getfreeworkerid returned $i" >> $DBG
         break
      fi
   done
   if [ $i -eq $[$THREADLIMIT+1] ];then
      #echo "no free threads" >> $DBG
      echo 0
      #exit
   else
      echo $i
   fi
}

updateIPC()
{
   COMPLETE=$(cat $IPC)        #read IPC
   COMPLETE=$[$COMPLETE+1]     #increment IPC
   echo $COMPLETE > $IPC       #write back to IPC
}


worker()
{
   WORKERID=$1
   THREADID=$2
   #echo "new worker WORKERID:$WORKERID THREADID:$THREADID" >> $DBG

   #accessing common terminal requires critical blocking section
   (flock -x -w 10 201
      trackthread $WORKERID $THREADID setactive
   )201>/tmp/$ME.lock

   let "RND = $RANDOM % $MAXTHREADDUR +1"

   for s in $(seq 1 $RND)               #simulate random lifespan
   do
      sleep 1;
      (flock -x -w 10 201
         trackthread $WORKERID $THREADID update $s
      )201>/tmp/$ME.lock
   done

   (flock -x -w 10 201
      trackthread $WORKERID $THREADID setfree
   )201>/tmp/$ME.lock

   (flock -x -w 10 201
      updateIPC
   )201>/tmp/$ME.lock
}

threadcount()
{
   TC=$(ls /tmp/$ME.$F1* 2> /dev/null | wc -l)
   #echo threadcount is $TC >> $DBG
   THREADCOUNT=$TC
   echo $TC
}

status()
{
   #summary status line
   COMPLETE=$(cat $IPC)
   plot 1 $[$THREADLIMIT+2] "WORKERS $(threadcount)/$THREADLIMIT  SPAWNED $SPAWNED/$SPAWN  COMPLETE $COMPLETE/$SPAWN SF=$SPEEDFACTOR TIMING=$TPS"
   echo -en '\033[K'                   #clear to end of line
}

function main()
{
   while [ $SPAWNED -lt $SPAWN ]
   do
      while [ $(threadcount) -lt $THREADLIMIT ] && [ $SPAWNED -lt $SPAWN ]
      do
         WID=$(getfreeworkerid)
         worker $WID $SPAWNED &
         touch /tmp/$ME.$F1$WID    #if this loops faster than file creation in the worker thread it steps on itself, thread tracking is best in main loop
         SPAWNED=$[$SPAWNED+1]
         (flock -x -w 10 201
            status
         )201>/tmp/$ME.lock
         sleep $TPS
        if ((! $[$SPAWNED%100]));then
           #rethink thread timing every 100 threads
           threadspeed
        fi
      done
      sleep $TPS
   done

   while [ "$(threadcount)" -gt 0 ]
   do
      (flock -x -w 10 201
         status
      )201>/tmp/$ME.lock
      sleep 1;
   done

   status
}

clear
threadspeed
main
wait
status
echo

0

Il tuo script dovrebbe apparire come:

prog1 &
prog2 &
.
.
progn &
wait
progn+1 &
progn+2 &
.
.

Supponendo che il sistema possa richiedere n lavori alla volta. usare wait per eseguire solo n lavori alla volta.


-1

Con bashj ( https://sourceforge.net/projects/bashj/ ), dovresti essere in grado di eseguire non solo più processi (come suggerito da altri) ma anche più thread in una JVM controllata dal tuo script. Ma ovviamente questo richiede un JDK Java. I thread consumano meno risorse dei processi.

Ecco un codice funzionante:

#!/usr/bin/bashj

#!java

public static int cnt=0;

private static void loop() {u.p("java says cnt= "+(cnt++));u.sleep(1.0);}

public static void startThread()
{(new Thread(() ->  {while (true) {loop();}})).start();}

#!bashj

j.startThread()

while [ j.cnt -lt 4 ]
do
  echo "bash views cnt=" j.cnt
  sleep 0.5
done
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.