web-dev-qa-db-de.com

Wie können Sie mehrere Programme gleichzeitig mit einem Bash-Skript ausführen?

Ich versuche, eine .sh-Datei zu schreiben, die viele Programme gleichzeitig ausführt

Ich habe es versucht

prog1 
prog2

Aber das führt prog1 aus und wartet dann, bis prog1 endet und dann prog2 startet ...

Wie kann ich sie also parallel ausführen?

201
Betamoo
prog1 &
prog2 &
178
psmears

Wie wäre es mit:

prog1 & prog2 && fg

Dieser Wille:

  1. Starten Sie prog1.
  2. Schicken Sie es in den Hintergrund, aber drucken Sie die Ausgabe weiter.
  3. Starten Sie prog2 Und lassen Sie es im Vordergrund, damit Sie es mit ctrl-c Schließen können.
  4. Wenn Sie prog2 Schließen, kehren Sie zum Vordergrund von prog1 Zurück, sodass Sie es auch mit ctrl-c Schließen können.
266
Ory Band

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

Erfahren Sie mehr:

59
Ole Tange

Sie können wait verwenden:

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

Es ordnet die PIDs des Hintergrundprogramms Variablen zu ($! ist der zuletzt gestartete Prozess (PID), dann wartet der Befehl wait auf sie. Es ist schön, denn wenn Sie das Skript beenden, werden auch die Prozesse beendet!

54
trusktr

Wenn Sie in der Lage sein möchten, mehrere Prozesse einfach mit ctrl-c Auszuführen und abzubrechen, ist dies meine Lieblingsmethode: Mehrere Hintergrundprozesse in einer (…) - Subshell erzeugen und SIGINT abfangen führe kill 0 aus, was alles, was in der Subshell-Gruppe auftaucht, tötet:

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

Sie können komplexe Prozessausführungsstrukturen haben und alles wird mit einem einzelnen ctrl-c Geschlossen (stellen Sie nur sicher, dass der letzte Prozess im Vordergrund ausgeführt wird, dh fügen Sie nach & Kein prog1.3 Ein = CODE!]):

(trap 'kill 0' SIGINT; prog1.1 && prog1.2 & (prog2.1 | prog2.2 || prog2.3) & prog1.3)
21
Quinn Comendant
#!/bin/bash
prog1 & 2> .errorprog1.log; prog2 & 2> .errorprog2.log

Leiten Sie Fehler in separate Protokolle um.

9
fermin

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

     Nohup - run a command immune to hangups, with output to a non-tty
8
3h4x

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

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)..."
  "[email protected]" && 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.

7
arnaldocan

Sie können versuchen ppss . 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 durchführen müssen.

7
ljt

Mit xargs -P <n> Können Sie <n> - Befehle parallel ausführen.

Während -P Keine Standardoption ist, wird sie sowohl von der GNU (Linux) - als auch von der macOS/BSD-Implementierung unterstützt.

Das folgende Beispiel:

  • führt höchstens 3 Befehle gleichzeitig 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 Befehl xargs selbst wird nicht zurückgegeben, bis alle Befehle beendet sind. Sie können ihn jedoch im Hintergrund ausführen, indem Sie ihn mit dem Steueroperator & Beenden und dann die integrierte Funktion wait verwenden warten, bis der gesamte Befehl xargs beendet 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:

  • In BSD/macOS xargs müssen Sie die Anzahl der Befehle angeben, die parallel ausgeführt werden sollen explizit, während Sie GNU xargs angeben können -P 0, Um möglichst viele parallel auszuführen.

  • Die Ausgabe der Prozesse, die parallel ausgeführt werden, kommt an während sie generiert wird, daher wird sie unvorhersehbar verschachtelt.

    • GNU parallel, wie in Ole's Antwort (kommt nicht standardmäßig mit den meisten Plattformen), bequem serialisiert (gruppiert) die Ausgabe pro Prozess und bietet viele erweiterte Funktionen.
7
mklement0

Vor kurzem hatte ich eine ähnliche Situation, in der ich mehrere Programme gleichzeitig ausführen, ihre Ausgaben in getrennte Protokolldateien umleiten und auf deren Abschluss warten musste. 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/de/execute-multiple-programs-and-redirect-their-outputs-linux/

7
Joaopcribeiro

Process Spawning Manager

Sicher, technisch gesehen handelt es sich um Prozesse, und dieses Programm sollte eigentlich als Prozess-Laich-Manager bezeichnet werden. Dies liegt jedoch nur an der Funktionsweise von BASH, wenn es nach dem kaufmännischen Und-Zeichen fragt und den Systemaufruf fork () oder vielleicht clone () verwendet Dies klont in einen separaten Speicherbereich anstatt in etwas wie pthread_create (), das sich den Speicher teilt. Wenn BASH das letztere unterstützt, würde jede "Ausführungssequenz" genauso ablaufen und könnte als traditioneller Thread bezeichnet werden, während ein effizienterer Speicherbedarf erzielt wird. Funktionell funktioniert es jedoch genauso, obwohl es etwas schwieriger ist, da GLOBAL-Variablen nicht in jedem Worker-Klon verfügbar sind, weshalb die prozessübergreifende Kommunikationsdatei und das rudimentäre Flock-Semaphor zum Verwalten kritischer Abschnitte verwendet werden. Das Gabeln von BASH ist natürlich die grundlegende Antwort, aber ich habe das Gefühl, als ob die Leute das wissen, aber wirklich versuchen, das zu verwalten, was erzeugt wird, anstatt es einfach abzweigen und es zu vergessen. Dies zeigt eine Möglichkeit zum Verwalten von bis zu 200 Instanzen gegabelter Prozesse, die alle auf eine einzelne Ressource zugreifen. Klar, das ist ü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
3
Josiah DeWitt