【问题标题】:How do you run multiple programs in parallel from a bash script?如何从 bash 脚本并行运行多个程序?
【发布时间】:2011-03-01 14:15:44
【问题描述】:

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

我试过了

prog1 
prog2

但是运行 prog1 然后等到 prog1 结束然后启动 prog2...

那么我怎样才能并行运行它们呢?

【问题讨论】:

    标签: bash parallel-processing


    【解决方案1】:

    并行运行多个程序:

    prog1 &
    prog2 &
    

    如果您需要脚本等待程序完成,您可以添加:

    wait
    

    在您希望脚本等待它们的位置。

    【讨论】:

    • 别忘了wait!是的,在 bash 中你可以等待脚本的子进程。
    • 另一种选择是使用nohup来防止程序在shell挂起时被杀死。
    • @liang:是的,它也适用于三个或更多程序。
    • 也许是一个愚蠢的问题,但如果我想运行prog1 | something & prog2 | another &,该怎么办?我很确定它不会起作用。
    • @Micha93:它工作得很好;为什么你认为它不会?
    【解决方案2】:
    #!/bin/bash
    prog1 & 2> .errorprog1.log; prog2 & 2> .errorprog2.log
    

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

    【讨论】:

    • 您必须在重定向之后放置 & 符号并省略分号(&符号也将执行命令分隔符的功能):prog1 2> .errorprog1.log & prog2 2> .errorprog2.log &
    • 分号执行两个命令,你可以测试 de bash 看看它是否正常工作;) 示例:pwd & 2> .errorprog1.log; echo "wop" & 2> .errorprog2.log 当你把 & 你把程序放在后台并立即执行下一个命令。
    • 它不起作用 - 错误不会被重定向到文件。试试:ls notthere1 & 2> .errorprog1.log; ls notthere2 & 2>.errorprog2.log。错误进入控制台,两个错误文件都是空的。正如@Dennis Williamson 所说,& 是一个分隔符,就像;,所以(a)它需要在命令末尾(在任何重定向之后),并且(b)你不需要@987654326完全是@ :-)
    【解决方案3】:

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

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

    【讨论】:

    • nohup 本身不会在后台运行任何东西,使用nohup 不是在后台运行任务的要求或先决条件。它们通常一起有用,但因此,这并不能回答问题。
    【解决方案4】:

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

    【讨论】:

      【解决方案5】:

      使用 GNU Parallel http://www.gnu.org/software/parallel/ 很简单:

      (echo prog1; echo prog2) | parallel
      

      或者,如果您愿意:

      parallel ::: prog1 prog2
      

      了解更多:

      【讨论】:

      • 值得注意的是parallel有不同的版本,语法不同。例如,在 Debian 衍生产品中,moreutils 软件包包含一个名为 parallel 的不同命令,其行为完全不同。
      • parallel 比使用& 更好吗?
      • @OptimusPrime 这真的取决于。 GNU Parallel 引入了一些开销,但作为回报,您可以更好地控制正在运行的作业和输出。如果同时打印两个作业,GNU Parallel 将确保输出没有混合。
      • @OptimusPrime parallel 在作业多于内核时会更好,在这种情况下,& 将在每个内核一次运行多个作业。 (参见pigeonhole principle
      • @OleTange "你的命令行会因此而爱你。" 我也是。 ☺
      【解决方案6】:

      怎么样:

      prog1 & prog2 && fg
      

      这将:

      1. 开始prog1
      2. 将其发送到后台,但继续打印其输出。
      3. 启动prog2,然后将其保留在前台,这样您就可以使用ctrl-c 将其关闭。
      4. 当你关闭prog2时,你会回到prog1前台,所以你也可以用ctrl-c关闭它。

      【讨论】:

      • prog2 终止时,有没有一种简单的方法可以终止prog1?想想node srv.js & cucumberjs
      • 刚刚试过这个,但它并没有像我预期的那样工作。但是,稍作修改:prog1 & prog2 ; fg 这是为了同时运行多个 ssh 隧道。希望这对某人有所帮助。
      • @jnadro52 您的解决方案的效果是,如果prog2 无法立即运行,您将回到前台使用prog1。如果这是可取的,那么没关系。
      • 在 SSH 的 shell 如果你执行这样的命令,杀死 prog1 会很棘手。 Ctrl-c 对我不起作用。甚至杀死整个终端也会让 prog1 继续运行。
      • @jnadro52 一次终止两个进程的方法是prog1 & prog2 && kill $!
      【解决方案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/

      【讨论】:

        【解决方案8】:

        你可以使用wait:

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

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

        【讨论】:

        • In my experience,杀死等待不会同时杀死其他进程。
        • 如果我在循环中启动后台进程,我该如何等待每个后台进程完成,然后再继续执行下一组命令。 #!/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 我认为您可以将进程 ID 保存到数组中,然后在数组上调用 wait 。我认为您必须使用${} 将其插入到字符串列表或类似列表中。
        • 最好的答案,对我来说,杀死脚本也会杀死进程! macOS Catalina、zsh 控制台
        • 使用wait 无法杀死我的第二个进程。
        【解决方案9】:

        这是我用来以最大 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 设置为核心数,此函数将尽量避免空闲核心。

        【讨论】:

        • 不错的 sn-p,但我在 bash 下找不到“wait -n”的解释,它说这是一个无效的选项。错字还是我遗漏了什么?
        • @EmmanuelDevaux: wait -n 需要 bash 4.3+ 并将逻辑更改为等待 any 的指定/隐含进程终止。
        • 如果其中一项任务失败了,我想结束脚本怎么办?
        • @52coder 你可以调整函数来捕捉失败的孩子,比如: "$@" && time2=$(date +"%H:%M:%S") && echo "finishing $2 ($time1 -- $time2)..." ||错误=1 &。然后在“if”部分测试错误并在需要时中止函数
        【解决方案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 命令本身在所有命令完成之前不会返回,但您可以在后台执行它,方法是使用控制运算符 &amp; 终止它,然后使用内置的 wait 等待整个 @ 987654330@命令完成。

        {
          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,正如Ole's answer 中提到的(大多数平台都没有不是标准),方便序列化(分组)每个进程的输出基础并提供许多更高级的功能。

        【讨论】:

          【解决方案11】:

          进程生成管理器

          当然,从技术上讲,这些是进程,并且这个程序实际上应该被称为进程生成管理器,但这只是由于 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
          

          【讨论】:

            【解决方案12】:

            使用 bashj (https://sourceforge.net/projects/bashj/),您不仅应该能够运行多个 进程(其他人建议的方式),而且还应该能够运行多个 线程 em> 在一个由您的脚本控制的 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
            

            【讨论】:

              【解决方案13】:

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

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

              你可以有复杂的进程执行结构,所有的事情都会以一个ctrl-c结束(只要确保最后一个进程在前台运行,即不要在prog1.3之后包含&amp;):

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

              【讨论】:

              • 这是迄今为止最好的答案。
              • kill 0 是什么? PID 0 是 subshel​​l 本身吗?
              • @mpen 没错,kill 程序将0 解释为“当前进程组中的所有进程都已发出信号。” man page 包含此描述。
              • 太棒了,工作得很好。这是一个很好的例子,子shell 是多么有用。
              • 我不得不使用trap 'kill 0' INT; 而不是SIGINT
              【解决方案14】:

              您的脚本应如下所示:

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

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

              【讨论】:

                【解决方案15】:

                如果你是:

                • 在 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
                

                【讨论】:

                • 这太棒了,它可以在普通终端上使用吗?
                【解决方案16】:

                这对我来说效果很好 (found here):

                sh -c 'command1 & command2 & command3 & wait'
                

                它输出混合的每个命令的所有日志(这是我想要的),并且都被 ctrl+c 杀死。

                【讨论】:

                  猜你喜欢
                  • 2010-10-13
                  • 2018-11-26
                  • 2023-04-07
                  • 1970-01-01
                  • 2021-11-30
                  • 2023-01-13
                  • 1970-01-01
                  • 1970-01-01
                  相关资源
                  最近更新 更多