Jak uruchamiasz wiele programów równolegle ze skryptu bash?


245

Próbuję napisać plik .sh, który uruchamia wiele programów jednocześnie

Próbowałem tego

prog1 
prog2

Ale to uruchamia program prog1, następnie czeka do zakończenia programu prog1, a następnie rozpoczyna program prog2 ...

Jak mogę je uruchomić równolegle?

Odpowiedzi:


216
prog1 &
prog2 &

49
Nie zapomnij wait! Tak, w bash możesz poczekać na procesy potomne skryptu.
Dummy00001

5
Inną opcją jest użycie, nohupaby zapobiec zabiciu programu podczas rozłączania się powłoki.
Philipp

@liang: Tak, będzie działać również z trzema lub więcej programami.
psmears

302

Co powiesz na:

prog1 & prog2 && fg

Spowoduje to:

  1. Początek prog1.
  2. Wyślij go w tło, ale drukuj dalej.
  3. Zacznij prog2i trzymaj go na pierwszym planie , abyś mógł go zamknąć ctrl-c.
  4. Po zamknięciu prog2, można powrócić do prog1„s planie , dzięki czemu można również zamknąć go z ctrl-c.

9
Czy istnieje prosty sposób, aby zakończyć prog1, gdy prog2zakończone jest? Pomyśl o node srv.js & cucumberjs
JP

20
Po prostu spróbowałem tego i nie działało to zgodnie z oczekiwaniami. Jednak niewielka modyfikacja zadziałała: prog1 & prog2 ; fg służyła do uruchamiania wielu tuneli ssh jednocześnie. Mam nadzieję, że to komuś pomoże.
jnadro52

2
@ jnadro52 Twoje rozwiązanie powoduje, że jeśli prog2nie uruchomi się natychmiast, wrócisz do posiadania prog1na pierwszym planie. Jeśli jest to pożądane, to jest w porządku.
Ory Band

3
W powłoce SSH Jeśli wykonasz takie polecenie, zabicie prog1 będzie trudne. Ctrl-c nie działało dla mnie. Nawet zabicie całego terminalu sprawiło, że uruchomiono prog1.
mercury0114

14
@ jnadro52 Istnieje sposób na zakończenie obu procesów jednocześnie prog1 & prog2 && kill $!.
zaboco

79

Możesz użyć wait:

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

Przypisuje PID programu w tle do zmiennych ( $!jest to PID ostatniego uruchomionego procesu), a następnie waitpolecenie czeka na nie. To miłe, ponieważ jeśli zabijesz skrypt, to również zabije procesy!


4
Z mojego doświadczenia wynika, że ​​zabijanie czekania nie zabija również innych procesów.
Quinn Comendant

1
Jeśli zaczynam procesy w tle w pętli, w jaki sposób mogę poczekać na zakończenie każdego procesu w tle, zanim przejdę do przodu z wykonaniem następnego zestawu poleceń. #!/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 Myślę, że możesz zapisać identyfikatory procesów w tablicy, a następnie wywołać funkcję wait na tablicy. Myślę, że musisz użyć, ${}aby interpolować go do listy ciągów lub podobnej.
trusktr

najlepsza odpowiedź, a dla mnie zabicie skryptu zabija również procesy! macOS Catalina, konsola zsh
Michael Klishevich

67

Z GNU Parallel http://www.gnu.org/software/parallel/ jest to tak proste, jak:

(echo prog1; echo prog2) | parallel

Lub jeśli wolisz:

parallel ::: prog1 prog2

Ucz się więcej:


4
Warto zauważyć, że istnieją różne wersje parallelo różnej składni. Na przykład w pochodnych Debiana moreutilspakiet zawiera inną komendę o nazwie, parallelktóra zachowuje się zupełnie inaczej.
Joel Cross,

4
jest parallellepsze niż używanie &?
Optimus Prime

2
@OptimusPrime To naprawdę zależy. GNU Parallel wprowadza pewne koszty ogólne, ale w zamian daje znacznie większą kontrolę nad uruchomionymi zadaniami i wynikami. Jeśli jednocześnie drukowane będą dwa zadania, GNU Parallel upewni się, że dane wyjściowe nie będą mieszane.
Ole Tange

1
@OptimusPrime paralleljest lepszy, gdy jest więcej zadań niż rdzeni, w którym to przypadku &można uruchomić więcej niż jedno zadanie na rdzeń jednocześnie. (por. zasada szuflady )
Geremia

2
@OleTange „ Twoja linia poleceń będzie cię za to kochać. ” Ja też. ☺
Geremia

55

Jeśli chcesz mieć możliwość łatwego uruchamiania i zabijania wielu procesów ctrl-c, jest to moja ulubiona metoda: spawnuj wiele procesów w tle w (…)podpowłoce i pułapkę SIGINTdo wykonania kill 0, która zabije wszystko odrodzone w grupie podpowłoki:

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

Możesz mieć złożone struktury wykonawcze procesów, a wszystko zamknie się za pomocą jednego ctrl-c(upewnij się, że ostatni proces jest uruchomiony na pierwszym planie, tzn. Nie dołączaj &po nim prog1.3):

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

Jak dotąd najlepsza odpowiedź.
Nic

10

xargs -P <n>pozwala na uruchamianie <n>poleceń równolegle.

Chociaż -Pjest to niestandardowa opcja, zarówno implementacje GNU (Linux), jak i macOS / BSD ją obsługują.

Poniższy przykład:

  • uruchamia maksymalnie 3 polecenia jednocześnie,
  • z dodatkowymi komendami uruchamianymi tylko po zakończeniu wcześniej uruchomionego procesu.
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

Dane wyjściowe wyglądają mniej więcej tak:

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

Czas pokazuje, że polecenia były uruchamiane równolegle (ostatnie polecenie zostało uruchomione dopiero po zakończeniu pierwszego z 3 oryginalnych, ale wykonane bardzo szybko).

Samo xargspolecenie nie powróci, dopóki wszystkie polecenia nie zostaną zakończone, ale można je wykonać w tle, kończąc je operatorem sterowania, &a następnie używając waitwbudowanego programu, aby czekać na zakończenie całego xargspolecenia.

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

Uwaga:

  • BSD / MacOS xargswymaga, aby określić liczbę poleceń uruchomić równolegle wyraźnie , natomiast GNU xargspozwala określić -P 0uruchomić jak najwięcej jak to możliwe równolegle.

  • Dane wyjściowe z procesów przebiegających równolegle docierają w miarę ich generowania , więc będą nieprzewidywalnie przeplatane .

    • GNU parallel, jak wspomniano w odpowiedzi Ole ( nie jest standardem w większości platform), wygodnie serializuje (grupuje) dane wyjściowe dla poszczególnych procesów i oferuje wiele bardziej zaawansowanych funkcji.

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

Błędy przekierowujące do oddzielnych dzienników.


13
Trzeba wstawić znaki ampersand po przekierowaniach i pomijać średnik (znak amp pełni również funkcję separatora poleceń):prog1 2> .errorprog1.log & prog2 2> .errorprog2.log &
Wstrzymano do odwołania.

średnik wykonuje obie komendy, możesz przetestować de bash, aby zobaczyć, czy działa dobrze;) Przykład: pwd & 2> .errorprog1.log; echo "wop" & 2> .errorprog2.log po umieszczeniu i umieszczeniu programu w tle i natychmiastowym wykonaniu następnego polecenia.
fermin

2
To nie działa - błędy nie są przekierowywane do pliku. Spróbuj z: ls notthere1 & 2> .errorprog1.log; ls notthere2 & 2>.errorprog2.log. Błędy trafiają do konsoli, a oba pliki błędów są puste. Jak mówi @Dennis Williamson, &jest separatorem, ;więc (a) musi przejść na końcu polecenia (po każdym przekierowaniu) i (b) wcale nie potrzebujesz ;:-)
psmears

8

Istnieje bardzo przydatny program, który wywołuje nohup.

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

4
nohupsam w sobie nie uruchamia niczego w tle, a używanie nohupnie jest wymaganiem ani warunkiem wstępnym do uruchamiania zadań w tle. Często są one przydatne razem, ale jako takie nie odpowiadają na pytanie.
tripleee

8

Oto funkcja, której używam, aby uruchomić równolegle maksymalnie n procesu (n = 4 w przykładzie):

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

Jeśli parametr max_children jest ustawiony na liczbę rdzeni, funkcja ta spróbuje uniknąć wolnych rdzeni.


1
Niezły fragment, ale nie mogę znaleźć wyjaśnienia „czekaj -n” pod moim bashem, mówi, że jest to nieprawidłowa opcja. literówka czy coś mi umknęło?
Emmanuel Devaux,

1
@EmmanuelDevaux: wait -nwymaga wersji bash4.3+ i zmienia logikę na oczekiwanie na zakończenie dowolnego z podanych / domyślnych procesów.
mklement0

co jeśli jedno z zadań się nie powiedzie, to chcę zakończyć skrypty?
52 koder

@ 52coder możesz dostosować funkcję przechwytywania nieudanego dziecka, na przykład: „$ @” && time2 = $ (data + „% H:% M:% S”) i& echo ”kończąc 2 $ ($ time1 - $ time2 ) ... ”|| błąd = 1 i. Następnie przetestuj pod kątem błędu w części „if” i przerwij funkcję, jeśli to konieczne
arnaldocan

7

Możesz spróbować ppss . ppss jest dość potężny - możesz nawet stworzyć mini-klaster. xargs -P może być również przydatny, jeśli masz do czynienia z kłopotliwie równoległym przetwarzaniem.


7

Ostatnio miałem podobną sytuację, w której musiałem uruchamiać wiele programów jednocześnie, przekierowywać ich wyniki do oddzielnych plików dziennika i czekać na ich zakończenie, a skończyło się na czymś takim:

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

Źródło: http://joaoperibeiro.com/execute-multiple-programs-and-redirect-their-outputs-linux/


4

Process Spawning Manager

Jasne, technicznie rzecz biorąc, są to procesy, a ten program powinien naprawdę nazywać się menedżerem spawnowania procesów, ale dzieje się tak tylko ze względu na sposób, w jaki BASH działa, gdy rozwidla się za pomocą ampersand, używa wywołania systemowego fork () lub clone () który klonuje do oddzielnej przestrzeni pamięci zamiast czegoś takiego jak pthread_create (), który współdzieli pamięć. Gdyby BASH wspierał to drugie, każda „sekwencja wykonania” działałaby tak samo i mogłaby być nazwana tradycyjnym wątkiem, zyskując bardziej wydajny ślad pamięci. Funkcjonalnie jednak działa tak samo, choć nieco trudniej, ponieważ zmienne GLOBALNE nie są dostępne w każdym klonie roboczym, stąd użycie pliku komunikacji międzyprocesowej i podstawowego semafora stad do zarządzania krytycznymi sekcjami. Rozwidlenie od BASH jest oczywiście podstawową odpowiedzią tutaj, ale czuję się tak, jakby ludzie to wiedzieli, ale naprawdę chcą zarządzać tym, co się pojawiło, zamiast tylko rozwidlać je i zapomnieć. Pokazuje to sposób zarządzania nawet 200 wystąpieniami rozwidlonych procesów, z których wszystkie uzyskują dostęp do jednego zasobu. Najwyraźniej to przesada, ale podobało mi się pisanie, więc kontynuowałem. Zwiększ odpowiednio rozmiar swojego terminala. Mam nadzieję, że uznasz to za przydatne.

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

Twój skrypt powinien wyglądać następująco:

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

Zakładając, że twój system może przyjmować n zadań jednocześnie. użyj funkcji wait, aby uruchomić tylko n zadań jednocześnie.


-1

Dzięki bashj ( https://sourceforge.net/projects/bashj/ ) powinieneś być w stanie uruchomić nie tylko wiele procesów (tak jak sugerowali inni), ale także wiele wątków w jednym JVM kontrolowanym ze skryptu. Ale oczywiście wymaga to JDK Javy. Wątki zużywają mniej zasobów niż procesy.

Oto działający kod:

#!/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
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.