Làm thế nào để bạn chạy nhiều chương trình song song từ một tập lệnh bash?


245

Tôi đang cố gắng viết một tệp .sh chạy nhiều chương trình cùng lúc

Tôi đã thử nó

prog1 
prog2

Nhưng nó chạy prog1 rồi đợi cho đến khi prog1 kết thúc và sau đó bắt đầu prog2 ...

Vậy làm thế nào tôi có thể chạy chúng song song?

Câu trả lời:


216
prog1 &
prog2 &

49
Đừng quên wait! Có, trong bash, bạn có thể đợi các tiến trình con của tập lệnh.
Dummy00001

5
Một tùy chọn khác là sử dụng nohupđể ngăn chương trình bị giết khi vỏ bị treo.
Philipp

@liang: Có, nó cũng sẽ hoạt động với ba chương trình trở lên.
psmears

302

Làm thế nào về:

prog1 & prog2 && fg

Điều này sẽ:

  1. Bắt đầu prog1.
  2. Gửi nó đến nền, nhưng tiếp tục in đầu ra của nó.
  3. Bắt đầu prog2giữ nó ở phía trước , để bạn có thể đóng nó với ctrl-c.
  4. Khi bạn gần prog2, bạn sẽ quay trở lại prog1của tiền cảnh , vì vậy bạn cũng có thể đóng nó với ctrl-c.

9
Có một cách dễ dàng để chấm dứt prog1khi prog2chấm dứt? Hãy nghĩ về node srv.js & cucumberjs
JP

20
Chỉ cần thử điều này, và nó không hoạt động như mong đợi đối với tôi. Tuy nhiên, một sửa đổi nhỏ đã hoạt động: prog1 & prog2 ; fg Điều này là để chạy nhiều đường hầm ssh cùng một lúc. Hy vọng điều này sẽ giúp được ai đó.
jnadro52

2
@ jnadro52 giải pháp của bạn có tác dụng là nếu prog2không chạy ngay lập tức, bạn sẽ quay trở lại để có prog1tiền cảnh. Nếu điều này là mong muốn, thì nó ổn.
Ban nhạc Ory

3
Trên vỏ SSH'ed Nếu bạn thực thi một lệnh như thế này, sẽ rất khó để giết prog1. Ctrl-c không hoạt động với tôi. Thậm chí giết chết toàn bộ thiết bị đầu cuối còn lại prog1 chạy.
thủy ngân0114

14
@ jnadro52 Một cách để chấm dứt cả hai quá trình cùng một lúc là prog1 & prog2 && kill $!.
zaboco

79

Bạn có thể sử dụng wait:

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

Nó gán các chương trình PID nền cho các biến ( $!là quá trình khởi chạy cuối cùng 'PID), sau đó waitlệnh chờ chúng. Thật tuyệt vì nếu bạn giết tập lệnh, nó cũng sẽ giết các tiến trình!


4
Theo kinh nghiệm của tôi , giết chờ cũng không giết các quy trình khác.
Quinn Comendant

1
Nếu tôi đang bắt đầu các quá trình nền trong một vòng lặp, làm thế nào tôi có thể đợi mọi quá trình nền hoàn thành trước khi chuyển tiếp với việc thực hiện các lệnh tiếp theo. #!/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 Tôi nghĩ bạn có thể lưu ID tiến trình vào một mảng, sau đó gọi chờ trên mảng. Tôi nghĩ bạn phải sử dụng ${}để nội suy nó thành một danh sách chuỗi hoặc tương tự.
trusktr

câu trả lời hay nhất và đối với tôi, việc giết chết kịch bản cũng giết chết các tiến trình! macOS Catalina, bảng điều khiển zsh
Michael Klishevich

67

Với GNU Parallel http://www.gnu.org/software/abul/ , thật dễ dàng như:

(echo prog1; echo prog2) | parallel

Hoặc nếu bạn thích:

parallel ::: prog1 prog2

Tìm hiểu thêm:


4
Điều đáng chú ý là có các phiên bản parallelkhác nhau với cú pháp khác nhau. Ví dụ, trên các dẫn xuất Debian, moreutilsgói chứa một lệnh khác được gọi là parallelhành vi hoàn toàn khác.
Joel Cross

4
paralleltốt hơn so với sử dụng &?
Optimus Prime

2
@OptimusPrime Nó thực sự phụ thuộc. GNU Parallel giới thiệu một số chi phí hoạt động, nhưng bù lại, cho phép bạn kiểm soát nhiều hơn các công việc và đầu ra đang chạy. Nếu hai công việc in cùng một lúc, GNU Parallel sẽ đảm bảo đầu ra không bị trộn lẫn.
Ole Tange

1
@OptimusPrime paralleltốt hơn khi có nhiều công việc hơn lõi, trong trường hợp đó &sẽ chạy nhiều công việc trên mỗi lõi cùng một lúc. (xem nguyên tắc pigeonhole )
Geremia

2
@OleTange " Dòng lệnh của bạn sẽ yêu bạn vì điều đó. " Tôi cũng vậy. ☺
Geremia

55

Nếu bạn muốn có thể dễ dàng chạy và tiêu diệt nhiều tiến trình ctrl-c, đây là phương pháp ưa thích của tôi: sinh ra nhiều quá trình nền trong một (…)lớp con và bẫy SIGINTđể thực thi kill 0, sẽ giết tất cả mọi thứ sinh ra trong nhóm lớp con:

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

Bạn có thể có các cấu trúc thực thi quy trình phức tạp và mọi thứ sẽ đóng lại bằng một ctrl-c(chỉ cần đảm bảo quy trình cuối cùng được chạy ở nền trước, nghĩa là không bao gồm một &sau prog1.3):

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

Đây là câu trả lời tốt nhất cho đến nay.
Nic

10

xargs -P <n>cho phép bạn chạy <n>các lệnh song song.

Mặc dù -Plà một tùy chọn không chuẩn, cả hai triển khai GNU (Linux) và macOS / BSD đều hỗ trợ nó.

Ví dụ sau:

  • chạy tối đa 3 lệnh song song cùng một lúc,
  • với các lệnh bổ sung chỉ bắt đầu khi một quá trình khởi chạy trước đó kết thúc.
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

Đầu ra trông giống như:

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

Thời gian cho thấy các lệnh được chạy song song (lệnh cuối cùng được khởi chạy chỉ sau khi lệnh đầu tiên trong số 3 ban đầu bị chấm dứt, nhưng được thực thi rất nhanh).

Các xargslệnh riêng của mình sẽ không trở lại cho đến khi tất cả các lệnh đã kết thúc, nhưng bạn có thể thực hiện nó ở chế độ nền bằng cách chấm dứt nó với nhà điều hành kiểm soát &và sau đó sử dụng waitdựng sẵn để chờ đợi cho toàn bộ xargslệnh để kết thúc.

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

Ghi chú:

  • BSD / macOS xargsyêu cầu bạn chỉ định số lượng lệnh để chạy song song một cách rõ ràng , trong khi GNU xargscho phép bạn chỉ định -P 0chạy song song càng nhiều càng tốt .

  • Đầu ra từ các quá trình chạy song song đến khi nó được tạo ra , do đó nó sẽ bị xen kẽ một cách khó lường .

    • GNU parallel, như đã đề cập trong câu trả lời của Ole ( không đạt tiêu chuẩn với hầu hết các nền tảng), thuận tiện tuần tự hóa (các nhóm) đầu ra trên cơ sở mỗi quy trình và cung cấp nhiều tính năng nâng cao hơn.

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

Chuyển hướng lỗi sang các bản ghi riêng biệt.


13
Bạn phải đặt các ký hiệu sau khi chuyển hướng và bỏ dấu chấm phẩy (ký hiệu cũng sẽ thực hiện chức năng của một dấu tách lệnh):prog1 2> .errorprog1.log & prog2 2> .errorprog2.log &
Tạm dừng cho đến khi có thông báo mới.

dấu chấm phẩy thực thi cả hai lệnh, bạn có thể kiểm tra de bash để thấy nó hoạt động tốt;) Ví dụ: pwd & 2> .errorprog1.log; echo "wop" & 2> .errorprog2.log khi bạn đặt & bạn đặt chương trình ở chế độ nền và thực hiện ngay lệnh tiếp theo.
fermin

2
Nó không hoạt động - các lỗi không được chuyển hướng đến tập tin. Hãy thử với : ls notthere1 & 2> .errorprog1.log; ls notthere2 & 2>.errorprog2.log. Các lỗi đi đến bàn điều khiển và cả hai tệp lỗi đều trống. Như @Dennis Williamson nói, &là một dấu phân cách, ;vì vậy, (a) nó cần phải đi đến cuối lệnh (sau bất kỳ chuyển hướng nào) và (b) bạn không cần ;gì cả :-)
psmears

8

Có một chương trình rất hữu ích gọi là nohup.

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

4
nohuptự nó không chạy bất cứ thứ gì trong nền và sử dụng nohupkhông phải là một yêu cầu hoặc điều kiện tiên quyết để chạy các tác vụ trong nền. Chúng thường hữu ích với nhau nhưng như vậy, điều này không trả lời câu hỏi.
tripleee

8

Đây là một hàm tôi sử dụng để chạy ở tiến trình max n song song (n = 4 trong ví dụ):

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

Nếu max_children được đặt thành số lõi, chức năng này sẽ cố gắng tránh các lõi nhàn rỗi.


1
Đoạn trích hay, nhưng tôi không thể tìm thấy lời giải thích về "chờ đợi" trong phần bash của tôi, nó nói rằng đó là một lựa chọn không hợp lệ. Lỗi đánh máy hay tôi đã bỏ lỡ điều gì?
Emmanuel Devaux

1
@EmmanuelDevaux: wait -nyêu cầu bash4.3+ và nó thay đổi logic để chờ bất kỳ quy trình được chỉ định / ngụ ý nào chấm dứt.
mkuity0

Điều gì xảy ra nếu một trong những nhiệm vụ thất bại, sau đó tôi muốn kết thúc các kịch bản?
52 mã hóa

@ 52coder bạn có thể điều chỉnh chức năng để bắt một đứa trẻ thất bại, đại loại như: "$ @" && time2 = $ (ngày + "% H:% M:% S") && echo "hoàn thành $ 2 ($ time1 - $ time2 ) ... "|| lỗi = 1 &. Sau đó kiểm tra lỗi trong phần "nếu" và hủy bỏ chức năng nếu cần
arnaldocan

7

Bạn có thể thử ppss . ppss khá mạnh - bạn thậm chí có thể tạo một cụm nhỏ. xargs -P cũng có thể hữu ích nếu bạn có một loạt xử lý song song lúng túng.


7

Gần đây tôi đã gặp một tình huống tương tự khi tôi cần chạy nhiều chương trình cùng một lúc, chuyển hướng đầu ra của chúng sang các tệp nhật ký riêng biệt và đợi chúng kết thúc và tôi đã kết thúc với một cái gì đó như thế:

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

Nguồn: http://joaoperibeiro.com/execute-multipl-programs-and-redirect-their-outputs-linux/


4

Quản lý sinh sản quá trình

Chắc chắn, về mặt kỹ thuật đây là các quy trình và chương trình này thực sự nên được gọi là trình quản lý sinh sản quy trình, nhưng điều này chỉ là do cách BASH hoạt động khi nó sử dụng ký hiệu, nó sử dụng lệnh gọi hệ thống fork () hoặc có thể là clone () mà nhân bản vào một không gian bộ nhớ riêng, thay vì một cái gì đó như pthread_create () sẽ chia sẻ bộ nhớ. Nếu BASH hỗ trợ cái sau, mỗi "chuỗi thực thi" sẽ hoạt động giống nhau và có thể được gọi là luồng truyền thống trong khi đạt được dấu chân bộ nhớ hiệu quả hơn. Tuy nhiên, về mặt chức năng, nó hoạt động như nhau, mặc dù khó khăn hơn một chút do các biến GLOBAL không có sẵn trong mỗi bản sao công nhân do đó sử dụng tệp truyền thông giữa các quá trình và semaphore thô sơ để quản lý các phần quan trọng. Ngã ba từ BASH tất nhiên là câu trả lời cơ bản ở đây nhưng tôi cảm thấy như mọi người biết điều đó nhưng thực sự đang tìm cách quản lý những gì được sinh ra thay vì chỉ rẽ nhánh và quên nó. Điều này cho thấy một cách để quản lý tới 200 trường hợp các quy trình rẽ nhánh tất cả truy cập vào một tài nguyên. Rõ ràng đây là quá mức cần thiết nhưng tôi thích viết nó vì vậy tôi tiếp tục. Tăng kích thước của thiết bị đầu cuối của bạn cho phù hợp. Tôi hy vọng bạn thấy nó hữu dụng.

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

Kịch bản của bạn sẽ giống như:

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

Giả sử hệ thống của bạn có thể nhận n công việc tại một thời điểm. sử dụng chờ để chỉ chạy n công việc tại một thời điểm.


-1

Với bashj ( https://sourceforge.net/projects/bashj/ ), bạn sẽ có thể chạy không chỉ nhiều quy trình (theo cách mà người khác đề xuất) mà còn nhiều Chủ đề trong một JVM được kiểm soát từ tập lệnh của bạn. Nhưng tất nhiên điều này đòi hỏi một JDK java. Chủ đề tiêu thụ ít tài nguyên hơn các quy trình.

Đây là một mã làm việc:

#!/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
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.