如何从bash脚本并行运行多个程序?

时间:2010-06-09 10:06:18

标签: bash parallel-processing

我正在尝试编写一个 .sh文件,它可以同时运行多个程序

我试过这个

prog1 
prog2

但是运行prog1然后等到prog1结束然后开始prog2 ...

那我怎么能并行运行呢?

16 个答案:

答案 0 :(得分:252)

怎么样:

prog1 & prog2 && fg

这将:

  1. 开始prog1
  2. 将其发送至后台,但继续打印输出。
  3. 启动prog2将其保留在前台,以便您可以使用ctrl-c关闭它。
  4. 当您关闭prog2时,您将返回prog1前景,因此您也可以使用ctrl-c关闭它。

答案 1 :(得分:169)

prog1 &
prog2 &

答案 2 :(得分:57)

使用GNU Parallel http://www.gnu.org/software/parallel/,它就像:

一样简单
(echo prog1; echo prog2) | parallel

或者如果您愿意:

parallel ::: prog1 prog2

了解详情:

答案 3 :(得分:51)

您可以使用wait

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

它将后台程序PID分配给变量($!是最后一个启动的进程'PID),然后wait命令等待它们。这很好,因为如果你杀死了脚本,它也会杀死进程!

答案 4 :(得分:16)

如果您希望能够使用ctrl-c轻松运行并杀死多个进程,这是我最喜欢的方法:在(…)子外壳中生成多个后台进程,并将SIGINT捕获到执行kill 0,这将杀死subshel​​l组中产生的所有内容:

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

您可以使用复杂的流程执行结构,并且所有内容都将以一个ctrl-c结尾(只要确保最后一个流程在前台运行即可,即在{之后不要包含& {1}}):

prog1.3

答案 5 :(得分:9)

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

将错误重定向到单独的日志。

答案 6 :(得分:8)

有一个非常有用的程序叫nohup。

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

答案 7 :(得分:7)

您可以尝试ppss。 ppss相当强大 - 你甚至可以创建一个迷你集群。 如果您有一批令人尴尬的并行处理,xargs -P也很有用。

答案 8 :(得分:7)

这是我用来并行运行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设置为内核数,则此函数将尝试避免空闲内核。

答案 9 :(得分:6)

我最近有类似的情况,我需要同时运行多个程序,将它们的输出重定向到分离的日志文件并等待它们完成,我最终得到类似的东西:

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

答案 10 :(得分:6)

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命令本身不会返回,直到所有命令都完成,但您可以在后台通过控制操作符&终止它然后使用{{1}来执行它} builtin等待整个wait命令完成。

xargs

注意:

  • BSD / macOS { 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 $! 要求您指定要并行运行的命令计数显式,而GNU xargs允许您指定xargs运行尽可能多的并行

  • 并行运行的进程的输出在生成时到达,因此它将不可预测地交错

    • GNU -P 0,如Ole's answer中所述(是大多数平台的标准配置),方便序列化(分组)输出基于每个流程并提供更多高级功能。

答案 11 :(得分:2)

处理产卵经理

当然,从技术上讲这些是进程,这个程序实际上应该被称为进程生成管理器,但这只是由于BASH使用&符号时它的工作方式,它使用fork()或者克隆( )系统调用克隆到一个单独的内存空间,而不是像共享内存的pthread_create()。如果BASH支持后者,每个&#34;执行顺序&#34;将运行相同,可以被称为传统线程,同时获得更高效的内存占用。但功能相同,虽然有点困难,因为GLOBAL变量在每个工作者克隆中都不可用,因此使用进程间通信文件和基本的flock信号量来管理关键部分。从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

答案 12 :(得分:0)

您的脚本应如下所示:

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

假设您的系统一次可以处理n个工作。使用wait一次只能运行n个作业。

答案 13 :(得分:0)

如果您是

  • 在Mac上安装iTerm
  • 要启动长期处于打开状态直到Ctrl + C的各种程序
  • 希望能够轻松查看每个进程的输出
  • 希望能够通过Ctrl + C轻松停止特定进程

如果您的用例是更多的应用程序监视/管理,则一个选项是编写终端本身的脚本。

例如,我最近做了以下操作。授予它特定于Mac,特定于iTerm的功能,并依赖于不推荐使用的Apple Script API(iTerm具有更新的Python选项)。它没有获得任何优雅奖,但可以完成工作。

#!/bin/sh
root_path="~/root-path"
auth_api_script="$root_path/auth-path/auth-script.sh"
admin_api_proj="$root_path/admin-path/admin.csproj"
agent_proj="$root_path/agent-path/agent.csproj"
dashboard_path="$root_path/dashboard-web"

osascript <<THEEND
tell application "iTerm"
  set newWindow to (create window with default profile)

  tell current session of newWindow
    set name to "Auth API"
    write text "pushd $root_path && $auth_api_script"
  end tell

  tell newWindow
    set newTab to (create tab with default profile)
    tell current session of newTab
        set name to "Admin API"
        write text "dotnet run --debug -p $admin_api_proj"
    end tell
  end tell

  tell newWindow
    set newTab to (create tab with default profile)
    tell current session of newTab
        set name to "Agent"
        write text "dotnet run --debug -p $agent_proj"
    end tell
  end tell

  tell newWindow
    set newTab to (create tab with default profile)
    tell current session of newTab
        set name to "Dashboard"
        write text "pushd $dashboard_path; ng serve -o"
    end tell
  end tell

end tell
THEEND

iTerm 2 screenshot multiple tabs result

答案 14 :(得分:-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

答案 15 :(得分:-1)

sh prog1;sh prog2

我认为这可行。