Wie können Sie mehrere Programme parallel über ein Bash-Skript ausführen?


245

Ich versuche eine .sh-Datei zu schreiben , in der viele Programme gleichzeitig ausgeführt werden

Ich habe es versucht

prog1 
prog2

Aber das läuft prog1, wartet dann bis prog1 endet und startet dann prog2 ...

Wie kann ich sie parallel ausführen?

Antworten:


216
prog1 &
prog2 &

49
Vergiss das nicht wait! Ja, in bash können Sie auf die untergeordneten Prozesse des Skripts warten.
Dummy00001

5
Eine andere Option besteht darin, nohupzu verhindern, dass das Programm beendet wird, wenn die Shell auflegt.
Philipp

@liang: Ja, es wird auch mit drei oder mehr Programmen funktionieren.
Psmears

302

Wie wäre es mit:

prog1 & prog2 && fg

Dieser Wille:

  1. Starten Sie prog1.
  2. Senden Sie es in den Hintergrund, aber drucken Sie die Ausgabe weiter.
  3. Starten Sie prog2und halten Sie es im Vordergrund , damit Sie es mit schließen können ctrl-c.
  4. Wenn Sie in der Nähe prog2, werden Sie zurückkommen prog1‚s Vordergrund , so können Sie auch in der Nähe mit ihm ctrl-c.

9
Gibt es eine einfache Möglichkeit, zu beenden, prog1wenn prog2beendet wird? Denken Sie an node srv.js & cucumberjs
JP

20
Ich habe es gerade versucht und es hat bei mir nicht wie erwartet funktioniert. Es funktionierte jedoch eine geringfügige Änderung: prog1 & prog2 ; fg Dies diente zum gleichzeitigen Ausführen mehrerer SSH-Tunnel. Hoffe das hilft jemandem.
jnadro52

2
@ jnadro52 Ihre Lösung hat den Effekt, dass prog2Sie wieder prog1im Vordergrund stehen , wenn sie nicht sofort ausgeführt wird . Wenn dies wünschenswert ist, ist es in Ordnung.
Ory Band

3
Auf SSH-Shell Wenn Sie einen Befehl wie diesen ausführen, ist es schwierig, prog1 zu beenden. Strg-C hat bei mir nicht funktioniert. Sogar das Töten des gesamten Terminals ließ prog1 laufen.
mercury0114

14
@ jnadro52 Eine Möglichkeit, beide Prozesse gleichzeitig zu beenden, ist prog1 & prog2 && kill $!.
Zaboco

79

Sie können verwenden wait:

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

Es weist die Hintergrundprogramm-PIDs Variablen zu ( $!ist die PID des zuletzt gestarteten Prozesses), und der waitBefehl wartet auf sie. Es ist schön, denn wenn Sie das Skript beenden, werden auch die Prozesse beendet!


4
Nach meiner Erfahrung tötet das Töten des Wartens nicht auch die anderen Prozesse.
Quinn Comendant

1
Wenn ich Hintergrundprozesse in einer Schleife starte, wie kann ich warten, bis jeder Hintergrundprozess abgeschlossen ist, bevor ich mit der Ausführung des nächsten Befehlssatzes fortfahre? #!/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 Ich denke, Sie können die Prozess-IDs in einem Array speichern und dann wait auf dem Array aufrufen. Ich denke, Sie müssen es verwenden, ${}um es in eine Zeichenfolgenliste oder ähnliches zu interpolieren.
Trusktr

Die beste Antwort, und für mich bringt das Töten des Skripts auch die Prozesse zum Erliegen! macOS Catalina, zsh console
Michael Klishevich

67

Mit GNU Parallel http://www.gnu.org/software/parallel/ ist es so einfach wie:

(echo prog1; echo prog2) | parallel

Oder wenn Sie es vorziehen:

parallel ::: prog1 prog2

Mehr erfahren:


4
Es ist erwähnenswert, dass es verschiedene Versionen parallelmit unterschiedlicher Syntax gibt. Bei Debian-Derivaten moreutilsenthält das Paket beispielsweise einen anderen Befehl, parallelder sich ganz anders verhält.
Joel Cross

4
ist parallelbesser als mit &?
Optimus Prime

2
@OptimusPrime Es kommt wirklich darauf an. GNU Parallel führt einen gewissen Overhead ein, bietet Ihnen jedoch im Gegenzug viel mehr Kontrolle über die ausgeführten Jobs und die Ausgabe. Wenn zwei Aufträge gleichzeitig gedruckt werden, stellt GNU Parallel sicher, dass die Ausgabe nicht gemischt wird.
Ole Tange

1
@OptimusPrime parallelist besser, wenn mehr Jobs als Kerne vorhanden sind. In diesem Fall &würden mehr als ein Job pro Kern gleichzeitig ausgeführt. (vgl. Pigeonhole-Prinzip )
Geremia

2
@OleTange " Ihre Kommandozeile wird Sie dafür lieben. " Ich auch. ☺
Geremia

55

Wenn Sie in der Lage sein möchten, mehrere Prozesse einfach auszuführen und zu beenden, ctrl-cist dies meine Lieblingsmethode: (…)Mehrere Hintergrundprozesse in einer Subshell erzeugen und Trap SIGINTausführen kill 0, wodurch alles getötet wird, was in der Subshell-Gruppe erzeugt wurde:

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

Sie können komplexe Prozessausführung Strukturen haben, und alles wird in der Nähe mit einem einzigen ctrl-c(nur sicherstellen , dass der letzte Prozess im Vordergrund ausgeführt wird, das heißt, sie sind nicht &nach prog1.3):

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

Dies ist bei weitem die beste Antwort.
Nic

10

xargs -P <n>ermöglicht es Ihnen, <n>Befehle parallel auszuführen.

Während -P eine nicht standardmäßige Option ist, unterstützen sie sowohl die GNU- (Linux) als auch die macOS / BSD-Implementierung.

Das folgende Beispiel:

  • läuft höchstens 3 Befehle gleichzeitig parallel aus,
  • Zusätzliche Befehle werden nur gestartet, wenn ein zuvor gestarteter Prozess beendet wird.
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

Die Ausgabe sieht ungefähr so ​​aus:

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

Das Timing zeigt, dass die Befehle parallel ausgeführt wurden (der letzte Befehl wurde erst gestartet, nachdem der erste der ursprünglichen 3 beendet wurde, aber sehr schnell ausgeführt wurde).

Der xargsBefehl selbst wird erst zurückgegeben, wenn alle Befehle abgeschlossen sind. Sie können ihn jedoch im Hintergrund ausführen, indem Sie ihn mit dem Steuerungsoperator beenden &und dann mit dem integrierten waitBefehl warten, bis der gesamte xargsBefehl abgeschlossen ist.

{
  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 $!

Hinweis:

  • Bei BSD / macOS xargsmüssen Sie die Anzahl der Befehle angeben, die explizit parallel ausgeführt werden sollen , während Sie bei GNU xargsangeben können -P 0, dass so viele Befehle wie möglich parallel ausgeführt werden sollen.

  • Die Ausgabe der parallel ausgeführten Prozesse kommt an, während sie generiert wird , sodass sie unvorhersehbar verschachtelt wird .

    • parallelWie in der Antwort von Ole erwähnt ( bei den meisten Plattformen nicht Standard), serialisiert (gruppiert) GNU die Ausgabe bequem pro Prozess und bietet viele erweiterte Funktionen.

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

Leiten Sie Fehler in separate Protokolle um.


13
Sie müssen das kaufmännische Und nach den Umleitungen einfügen und das Semikolon prog1 2> .errorprog1.log & prog2 2> .errorprog2.log &
weglassen

Wenn das Semikolon beide Befehle ausführt, können Sie de bash testen, um zu sehen, ob es gut funktioniert;) Beispiel: pwd & 2> .errorprog1.log; Echo "wop" & 2> .errorprog2.log, wenn Sie & Sie setzen das Programm in den Hintergrund und führen sofort den nächsten Befehl aus.
Ende

2
Es funktioniert nicht - die Fehler werden nicht in die Datei umgeleitet. Versuchen Sie es mit : ls notthere1 & 2> .errorprog1.log; ls notthere2 & 2>.errorprog2.log. Die Fehler gehen an die Konsole und beide Fehlerdateien sind leer. Wie @Dennis Williamson sagt, &ist ein Trennzeichen wie ;: (a) es muss am Ende des Befehls stehen (nach jeder Umleitung), und (b) Sie brauchen das ;überhaupt nicht :-)
psmears

8

Es gibt ein sehr nützliches Programm, das nohup aufruft.

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

4
nohupAn sich wird nichts im Hintergrund ausgeführt, und die Verwendung nohupist keine Voraussetzung oder Voraussetzung für die Ausführung von Aufgaben im Hintergrund. Sie sind oft zusammen nützlich, aber als solche beantwortet dies die Frage nicht.
Tripleee

8

Hier ist eine Funktion, die ich verwende, um parallel mit maximal n Prozessen zu arbeiten (im Beispiel n = 4):

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

Wenn max_children auf die Anzahl der Kerne eingestellt ist, versucht diese Funktion, Leerlaufkerne zu vermeiden.


1
Nettes Snippet, aber ich kann die Erklärung von "wait -n" nicht unter meiner Bash finden, es heißt, dass dies eine ungültige Option ist. Tippfehler oder habe ich etwas verpasst?
Emmanuel Devaux

1
@EmmanuelDevaux: wait -nerfordert bash4.3+ und ändert die Logik so, dass darauf gewartet wird, dass einer der angegebenen / implizierten Prozesse beendet wird.
mklement0

Was ist, wenn eine der Aufgaben fehlgeschlagen ist und ich die Skripte beenden möchte?
52coder

@ 52coder Sie können die Funktion anpassen, um ein fehlgeschlagenes Kind zu erfassen, etwa: "$ @" && time2 = $ (Datum + "% H:% M:% S") && echo "$ 2 beenden ($ time1 - $ time2 ) ... "|| Fehler = 1 &. Testen Sie dann den "if"
-Teil

7

Sie können ppss versuchen . ppss ist ziemlich leistungsfähig - Sie können sogar einen Mini-Cluster erstellen. xargs -P kann auch nützlich sein, wenn Sie eine Menge peinlich paralleler Verarbeitung zu erledigen haben.


7

Ich hatte kürzlich eine ähnliche Situation, in der ich mehrere Programme gleichzeitig ausführen, ihre Ausgaben in getrennte Protokolldateien umleiten und warten musste, bis sie fertig waren, und am Ende hatte ich Folgendes:

#!/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

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


4

Prozess-Laich-Manager

Sicher, technisch gesehen sind dies Prozesse, und dieses Programm sollte eigentlich als Prozess-Spawning-Manager bezeichnet werden, aber dies liegt nur an der Funktionsweise von BASH, wenn es das kaufmännische Und verwendet, den Systemaufruf fork () oder vielleicht clone () verwendet Dies klont in einen separaten Speicherbereich und nicht in pthread_create (), das den Speicher gemeinsam nutzen würde. Wenn BASH Letzteres unterstützen würde, würde jede "Ausführungssequenz" genauso funktionieren und könnte als herkömmliche Threads bezeichnet werden, während ein effizienterer Speicherbedarf erzielt wird. Funktionell funktioniert es jedoch genauso, wenn auch etwas schwieriger, da nicht in jedem Worker-Klon GLOBAL-Variablen verfügbar sind, weshalb die prozessübergreifende Kommunikationsdatei und das rudimentäre Flock-Semaphor zur Verwaltung kritischer Abschnitte verwendet werden. Das Gabeln von BASH ist hier natürlich die grundlegende Antwort, aber ich habe das Gefühl, dass die Leute das wissen, aber wirklich versuchen, das zu verwalten, was erzeugt wird, anstatt es nur zu gabeln und zu vergessen. Dies zeigt eine Möglichkeit, bis zu 200 Instanzen von gegabelten Prozessen zu verwalten, die alle auf eine einzelne Ressource zugreifen. Natürlich ist das übertrieben, aber ich habe es genossen, es zu schreiben, also habe ich weitergemacht. Erhöhen Sie die Größe Ihres Terminals entsprechend. Ich hoffe, Sie finden das nützlich.

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

Ihr Skript sollte folgendermaßen aussehen:

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

Angenommen, Ihr System kann n Jobs gleichzeitig annehmen. Verwenden Sie wait, um jeweils nur n Jobs auszuführen.


-1

Mit bashj ( https://sourceforge.net/projects/bashj/ ) sollten Sie nicht nur mehrere Prozesse ausführen können (wie von anderen vorgeschlagen), sondern auch mehrere Threads in einer JVM, die von Ihrem Skript aus gesteuert wird. Dies erfordert natürlich ein Java-JDK. Threads verbrauchen weniger Ressourcen als Prozesse.

Hier ist ein Arbeitscode:

#!/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
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.