Как вы запускаете несколько программ параллельно из bash-скрипта?

245

Я пытаюсь написать .sh файл, который запускает много программ одновременно

Я пробовал это

prog1 
prog2

Но это запускает prog1, затем ждет, пока prog1 заканчивается, а затем запускает prog2 ...

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

Betamoo
источник

Ответы:

216
prog1 &
prog2 &
psmears
источник
49
Не забывайте wait! Да, в bash вы можете дождаться дочерних процессов скрипта.
Dummy00001
5
Другой вариант - использовать, nohupчтобы не допустить уничтожения программы при зависании оболочки.
Филипп
@liang: Да, он будет работать с тремя и более программами тоже.
psmears
303

Как насчет:

prog1 & prog2 && fg

Это будет:

  1. Старт prog1.
  2. Отправьте его на задний план, но продолжайте печатать вывод.
  3. Начните prog2и держите его на переднем плане , чтобы вы могли закрыть его ctrl-c.
  4. Когда ты рядом prog2, вы будете возвращаться к prog1«S переднего плана , так что вы также можете закрыть его с ctrl-c.
Ory Band
источник
9
Есть ли простой способ прекратить, prog1когда prog2заканчивается? Подумайте о node srv.js & cucumberjs
JP
20
Просто попробовал это, и это не сработало, как ожидалось для меня. Тем не менее, небольшое изменение сработало: prog1 & prog2 ; fg это было для запуска нескольких туннелей SSH одновременно. Надеюсь, это кому-нибудь поможет.
jnadro52
2
@ jnadro52 ваше решение приводит к тому, что если сразу prog2не запустится, вы вернетесь к тому, что prog1на переднем плане. Если это желательно, тогда все в порядке.
Ory Band
3
В оболочке с SSH Если вы выполните такую ​​команду, убить prog1 будет непросто. Ctrl-c не работал для меня. Даже убивая весь терминал оставил прогу в работе.
mercury0114
15
@ jnadro52 Способ завершить оба процесса одновременно prog1 & prog2 && kill $!.
Забоко
80

Вы можете использовать wait:

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

Он назначает переменные PID фоновой программы (это PID $!последнего запущенного процесса), затем waitкоманда ожидает их. Это хорошо, потому что если вы убьете скрипт, он тоже убьет процессы!

trusktr
источник
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.."
Яш
@ Я думаю, вы можете сохранить идентификаторы процессов в массиве, а затем вызвать ожидание для массива. Я думаю, что вы должны использовать, ${}чтобы интерполировать его в список строк или тому подобное.
trusktr
лучший ответ, и для меня убийство сценария также убивает процессы! macOS Catalina, zsh console
Михаил Клишевич
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 parallel_tutorial). Ваша командная строка будет любить вас за это.
  • Читайте: Оле Танге, GNU Parallel 2018 (Оле Танге, 2018).
Оле Танге
источник
4
Стоит отметить, что существуют разные версии parallelс разным синтаксисом. Например, в производных Debian moreutilsпакет содержит другую команду с именем, parallelкоторая ведет себя совершенно иначе.
Джоэл Кросс
4
это parallelлучше , чем использовать &?
Оптимус Прайм
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

Время показывает, что команды были запущены параллельно (последняя команда была запущена только после того, как первая из трех оригинальных команд завершилась, но была выполнена очень быстро).

Сама 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, как упомянуто в ответе Оле ( не входит в стандартную комплектацию большинства платформ), удобно сериализует (группирует) вывод для каждого процесса и предлагает множество дополнительных функций.
mklement0
источник
9
#!/bin/bash
prog1 & 2> .errorprog1.log; prog2 & 2> .errorprog2.log

Перенаправлять ошибки в отдельные журналы.

Фермин
источник
13
Вы должны поставить амперсанды после перенаправлений и пропустить точку с запятой (амперсанд также будет выполнять функцию разделителя команд):prog1 2> .errorprog1.log & prog2 2> .errorprog2.log &
Приостановлено до дальнейшего уведомления.
точка с запятой выполняет обе команды, вы можете проверить de bash, чтобы убедиться, что она работает хорошо;) Пример: 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
3h4x
источник
4
nohupсам по себе ничего не запускает в фоновом режиме, и использование nohupне является обязательным или обязательным условием для запуска задач в фоновом режиме. Они часто полезны вместе, но как таковой, это не отвечает на вопрос.
tripleee
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 задано количество ядер, эта функция будет пытаться избежать незанятых ядер.

arnaldocan
источник
1
Хороший фрагмент, но я не могу найти объяснение "wait -n" в моем bash, которое говорит, что это неверная опция. опечатка или я что то пропустил?
Эммануэль Дево
1
@EmmanuelDevaux: wait -nтребуется bash4.3+, и он меняет логику на ожидание завершения любого из указанных / подразумеваемых процессов.
mklement0
что если одна из задач не удалась, то я хочу завершить сценарии?
52 кодировщика
@ 52кодер, вы можете настроить функцию для захвата дочернего объекта, например: "$ @" && time2 = $ (date + "% H:% M:% S") && echo ", заканчивая $ 2 ($ time1 - $ time2 ) ... "|| ошибка = 1 &. Затем проверьте на наличие ошибок в части «если» и, если необходимо,
прервите
7

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

LJT
источник
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/

Joaopcribeiro
источник
4

Менеджер процесса порождения

Конечно, технически это процессы, и эта программа должна называться диспетчером порождения процессов, но это только из-за того, что BASH работает, когда он разветвляется с помощью амперсанда, он использует системный вызов fork () или, возможно, clone () который клонируется в отдельное пространство памяти, а не что-то вроде pthread_create (), который бы разделял память. Если бы BASH поддерживал последнее, каждая «последовательность выполнения» работала бы одинаково и могла бы быть названа традиционными потоками, получая при этом более эффективный объем памяти. Функционально, однако, он работает так же, хотя и немного сложнее, поскольку переменные GLOBAL недоступны в каждом рабочем клоне, поэтому для управления критическими секциями используется файл межпроцессного взаимодействия и элементарный семафор стада. Формирование от BASH, конечно, является основным ответом здесь, но я чувствую, что люди знают это, но действительно хотят управлять тем, что порождено, а не просто раскошелиться и забыть это. Это демонстрирует способ управления до 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 заданий одновременно. используйте wait для запуска только n заданий одновременно.

amalik2205
источник
-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
Fil
источник