Як запускати кілька програм паралельно з bash script?


245

Я намагаюся написати .sh файл, який запускає багато програм одночасно

Я спробував це

prog1 
prog2

Але цей запуск prog1 потім чекає, поки prog1 закінчиться, а потім запустить prog2 ...

То як я можу їх паралельно запускати?

Відповіді:


216
prog1 &
prog2 &

49
Не забувайте wait! Так, в bash ви можете дочекатися дочірніх процесів сценарію.
Dummy00001

5
Іншим варіантом є використання nohupдля запобігання загибелі програми, коли оболонка зависла.
Філіп

@liang: Так, вона також працюватиме з трьома і більше програмами.
psmears

302

Як щодо:

prog1 & prog2 && fg

Це буде:

  1. Старт prog1.
  2. Відправте його у фоновий режим, але продовжуйте друкувати його вихід.
  3. Почніть prog2і тримайте його на передньому плані , щоб ви могли закрити його ctrl-c.
  4. Коли ти поруч prog2, ви будете повертатися до prog1«S переднього плану , так що ви також можете закрити його з ctrl-c.

9
Чи є простий спосіб припинити, prog1коли prog2припиняється? Подумайте node srv.js & cucumberjs
JP

20
Просто спробував це, і це не спрацювало так, як очікувалося для мене. Однак незначна модифікація спрацювала: prog1 & prog2 ; fg це було для запуску декількох ssh-тунелів одночасно. Сподіваюся, що це комусь допоможе.
jnadro52

2
@ jnadro52 ваше рішення призведе до того, що якщо prog2не вдасться негайно запуститись, ви повернетесь до того, що будете prog1на передньому плані. Якщо це бажано, то це нормально.
Ory Band

3
На оболонці SSH'ed Якщо ви виконаєте таку команду, вбити prog1 буде складно. Ctrl-c не працював для мене. Навіть вбиваючи весь термінал, лівий прог1 працює.
mercury0114

14
@ jnadro52 Спосіб припинення обох процесів одночасно є prog1 & prog2 && kill $!.
zaboco

79

Ви можете використовувати wait:

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

Він призначає PID-програми фонової програми змінним ( $!це PID останнього запущеного процесу), тоді waitкоманда чекає їх. Це приємно, тому що якщо ви вбиваєте сценарій, він також вбиває процеси!


4
На мій досвід , вбивство чекати не вбиває й інших процесів.
Квінн Комендант

1
Якщо я запускаю фонові процеси в циклі, як я можу дочекатися завершення кожного фонового процесу, перш ніж рухатись вперед із виконанням наступного набору команд. #!/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 Я думаю, що ви можете зберегти ідентифікатори процесу в масив, а потім зателефонувати зачекати на масив. Я думаю, що вам доведеться використовувати його ${}для інтерполяції у список рядків чи подібних.
trusktr

найкраща відповідь, і для мене вбивство сценарію теж вбиває процеси! macOS Каталіна, консоль zsh
Михайло Клишевич

67

З GNU Parallel http://www.gnu.org/software/parallel/ це так просто, як:

(echo prog1; echo prog2) | parallel

Або якщо ви віддаєте перевагу:

parallel ::: prog1 prog2

Вчи більше:

  • Перегляньте вступне відео для швидкого вступу: https://www.youtube.com/playlist?list=PL284C9FF2488BC6D1
  • Пройдіться по навчальному посібнику (man Parale_tutorial). Ваш командний рядок буде любити вас за це.
  • Читайте: Ole Tange, GNU Parallel 2018 (Ole Tange, 2018).

4
Варто зазначити, що існують різні версії parallelз різним синтаксисом. Наприклад, на похідних Debian moreutilsпакет містить іншу команду, parallelяка називається, яка поводиться зовсім інакше.
Джоель Крос

4
це parallelкраще , ніж використовувати &?
Optimus Prime

2
@OptimusPrime Це дійсно залежить. GNU Parallel вводить деякі накладні витрати, але взамін дає вам набагато більше контролю над виконаними роботами та результатами. Якщо одночасно друкуються два завдання, GNU Parallel переконається, що вихід не змішаний.
Оле Танге

1
@OptimusPrime parallelкраще, коли більше робочих місць, ніж ядер, і в цьому випадку &одночасно буде виконуватися більше одного завдання на ядро. (Див принцип закуток )
Geremia

2
@OleTange " Ваш командний рядок буде любити вас за це ". Я теж. ☺
Geremia

55

Якщо ви хочете мати можливість легко запускати та вбивати декілька процесів ctrl-c, це мій улюблений метод: породжувати декілька фонових процесів у (…)підрозділі та SIGINTвиконувати пастку kill 0, яка вбиватиме все, що породжене у групі підпакетів:

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

Ви можете мати складні структури виконання процесів, і все закриється єдиним ctrl-c(просто переконайтеся, що останній процес запущений на передньому плані, тобто не включайте &після prog1.3):

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

Це найкраща відповідь на сьогоднішній день.
Нік

10

xargs -P <n> дозволяє бігати <n> команди паралельно.

Поки -P це нестандартний варіант, і GNU (Linux), і macOS / BSD підтримують його.

Наступний приклад:

  • працює не більше 3 команди паралельно,
  • додаткові команди починаються лише тоді, коли завершений раніше запущений процес закінчується.
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

Вихід виглядає приблизно так:

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

Час показує, що команди виконувались паралельно (остання команда була запущена лише після того, як перша з оригінальних 3 була припинена, але виконується дуже швидко).

Сама xargsкоманда не повернеться до тих пір, поки всі команди не будуть закінчені, але ви можете виконати її у фоновому режимі, припинивши її керуючим оператором, &а потім за допомогою waitвбудованого дочекатися завершення всієї xargsкоманди.

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

Примітка:

  • BSD / MacOS xargsвимагає , щоб вказати кількість команд для запуску паралельно явно , в той час як GNU xargsдозволяє вказати -P 0запускати стільки , наскільки це можливо паралельно.

  • Вихід з процесів, що протікають паралельно, надходить у міру його генерування , так і буде непередбачувано переплетений .

    • GNU parallel, як згадується у відповіді Оле ( не стандартний для більшості платформ), зручно серіалізує (групує) вихід на основі процесу та пропонує багато більш досконалих функцій.

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

Перенаправлення помилок на розділення журналів.


13
Ви повинні поставити амперсанди після перенаправлень і залишити крапку з комою (амперсанд також виконуватиме функцію розділювача команд):prog1 2> .errorprog1.log & prog2 2> .errorprog2.log &
Призупинено до подальшого повідомлення.

крапкою з комою виконайте обидва команди, ви можете перевірити де-баш, щоб переконатися, що він працює добре;) Приклад: pwd & 2> .errorprog1.log; echo "wop" & 2> .errorprog2.log, коли ви кладете & ви ставите програму у фоновий режим і негайно виконуєте наступну команду.
Фермін

2
Це не працює - помилки не перенаправляються до файлу. Спробуйте з: ls notthere1 & 2> .errorprog1.log; ls notthere2 & 2>.errorprog2.log. Помилки переходять до консолі, і обидва файли помилок порожні. Як говорить @Dennis Williamson, &це роздільник, як-от ;, так (а) йому потрібно пройти в кінці команди (після будь-якого перенаправлення), і (б) вам зовсім не потрібно ;:-)
psmears

8

Є дуже корисна програма, яка викликає nohup.

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

4
nohupсам по собі нічого не працює у фоновому режимі, а використання nohupне є обов'язковою умовою для виконання завдань у фоновому режимі. Вони часто корисні разом, але як такі, це не відповідає на питання.
трійка

8

Ось функція, яку я використовую для того, щоб паралельно запускати процес max n (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

Якщо max_children встановлено кількість ядер, ця функція намагатиметься уникати простоїв ядер.


1
Хороший фрагмент, але я не можу знайти пояснення "зачекати -n" під моїм баштом, він говорить, що це недійсний варіант. друкарський помилок чи я щось пропустив?
Еммануель Дево

1
@EmmanuelDevaux: wait -nвимагає bash4.3+ і це змінює логіку очікування завершення будь-якого із зазначених / маються на увазі процесів.
mklement0

що, якщо одне завдання не вдалося, я хочу закінчити сценарії?
52кодер

@ 52coder ви можете налаштувати функцію зйомки невдалої дитини, щось на кшталт: "$ @" && time2 = $ (дата + "% H:% M:% S") && echo ", закінчивши $ 2 ($ time1 - $ time2 ) ... "|| помилка = 1 &. Потім перевірити на помилку в частині "якщо" і скасувати функцію, якщо потрібно
arnaldocan

7

Ви можете спробувати ppss . ppss досить потужний - можна навіть створити міні-кластер. xargs -P також може бути корисним, якщо у вас є партія незручно паралельної обробки.


7

Нещодавно у мене була подібна ситуація, коли мені потрібно було запускати декілька програм одночасно, перенаправляти свої виходи на окремі файли журналів і чекати, коли вони закінчаться, і я закінчив щось подібне:

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

Джерело: http://joaoperibeiro.com/execute-multiple-programs-and-redirect-their-outputs-linux/


4

Менеджер процесів нересту

Звичайно, технічно це процеси, і цю програму справді слід назвати менеджером нерестування процесів, але це лише завдяки тому, що BASH працює, коли він розщеплює за допомогою ampersand, він використовує системний виклик fork () або, можливо, клонування () який клонується в окремий простір пам’яті, а не щось на зразок pthread_create (), яке б обмінювало пам'ять. Якби BASH підтримував останнє, кожна «послідовність виконання» працювала б однаково і її можна було б назвати традиційними потоками, отримуючи при цьому більш ефективний слід пам’яті. Однак функціонально це працює так само, хоча і трохи складніше, оскільки змінні GLOBAL недоступні у кожному робочому клоні, отже, використання файлу зв'язку між процесами та семафору рудиментарної зграї для управління критичними секціями. Тут, звичайно, є основна відповідь, але я відчуваю, ніби люди це знають, але справді прагнуть керувати тим, що породиться, а не просто розщедритися і забути його. Це демонструє спосіб управління до 200 екземплярів роздвоєних процесів, які мають доступ до одного ресурсу. Ясна річ, що це надмірно, але мені сподобалося її писати, тому я продовжував продовжувати. Збільшити розмір свого терміналу відповідно. Я сподіваюся, що Ви вважаєте це корисним.

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

Ваш сценарій повинен виглядати так:

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

Припустимо, що ваша система може зайняти n завдань одночасно. використовувати чекати, щоб запустити лише n завдань одночасно.


-1

За допомогою bashj ( https://sourceforge.net/projects/bashj/ ) ви маєте змогу запускати не лише декілька процесів (так, як запропонували інші), але й декілька потоків в одному JVM, керованому зі свого сценарію. Але, звичайно, для цього потрібен java JDK. Нитки споживають менше ресурсів, ніж процеси.

Ось робочий код:

#!/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
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.