code

bash 스크립트에서 여러 프로그램을 병렬로 실행하려면 어떻게 해야 합니까?

starcafe 2023. 5. 13. 10:27
반응형

bash 스크립트에서 여러 프로그램을 병렬로 실행하려면 어떻게 해야 합니까?

여러 프로그램을 동시에 실행하는 .sh 파일을 작성하려고 합니다.

해봤습니다.

prog1 
prog2

하지만 prog1을 실행한 다음 prog1이 끝날 때까지 기다린 다음 prog2를 시작합니다.

그러면 어떻게 병렬로 실행할 수 있을까요?

어때요?

prog1 & prog2 && fg

다음 작업을 수행합니다.

  1. »prog1.
  2. 백그라운드로 보내되 출력은 계속 인쇄합니다.
  3. »prog2그리고 그것을 전경에 보관하여 당신이 그것을 닫을 수 있습니다.ctrl-c.
  4. 을 닫을 때 .prog2로 돌아갈 것입니다.prog1전경, 그래서 당신은 그것을 닫을 수도 있습니다.ctrl-c.

여러 프로그램을 병렬로 실행하려면:

prog1 &
prog2 &

프로그램이 완료될 때까지 기다리는 스크립트가 필요한 경우 다음을 추가할 수 있습니다.

wait

스크립트가 해당 사용자를 대기하도록 설정합니다.

여러 프로세스를 쉽게 실행하고 제거할 수 있도록 하려면ctrl-c이것은 내가 가장 좋아하는 방법입니다: 여러 백그라운드 프로세스를 a에서 생성합니다.(…) trap 하위 및트랩셸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)

될 수 다른 계속 하려면 마막명이일종다고모명른계다실면다추니려합가음을을 추가합니다.wait최의명령로으 예서에는제다입니다.sleep 2먼저 나갔을 거예요, 살인을 하고요.sleep 4완료되기 전에; 추가wait 다될 때까지 할 수 .

(trap 'kill 0' SIGINT; sleep 4 & sleep 2 & wait)

사용할 수 있습니다.wait:

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

에 할당합니다.$!된 프로세스 이고, 은 'PID'입니다.wait명령이 그들을 기다립니다.대본을 죽이면 과정도 죽여서 좋습니다!

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)를 살펴봅니다.당신의 명령줄은 당신을 좋아할 것입니다.
  • 읽기: Ole Tange, GNU Parallel 2018 (Ole Tange, 2018).

xargs -P <n>할 수 .<n>명령을 병렬로 입력합니다.

하는 동안에-P는 비표준 옵션이며, GNU(리눅스) 및 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/macOSxargs병렬로 실행할 명령 수를 명시적으로 지정해야 하지만 GNU는xargs할 수 .-P 0가능한 한 많은 것을 병렬로 실행하는 것.

  • 병렬로 실행되는 프로세스의 출력은 생성되는 대로 도착하므로 예측할 수 없는 인터리브가 됩니다.

    • GNUparallelOle의 답변에서 언급했듯이(대부분의 플랫폼에서는 표준으로 제공되지 않음), 출력을 프로세스별로 편리하게 직렬화(그룹화)하고 훨씬 더 고급 기능을 제공합니다.
#!/bin/bash
prog1 & 2> .errorprog1.log; prog2 & 2> .errorprog2.log

오류를 별도의 로그로 리디렉션합니다.

다음은 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을 코어 수로 설정하면 이 함수는 유휴 코어를 피하려고 합니다.

이 기능은 제게 매우 적합합니다(여기 참조).

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

혼합된 각 명령의 모든 로그를 출력합니다(이것이 제가 원하는 것입니다). 모든 로그는 ctrl+c로 삭제됩니다.

노업을 부르는 매우 유용한 프로그램이 있습니다.

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

최근에 여러 프로그램을 동시에 실행하고 출력을 별도의 로그 파일로 리디렉션하여 완료될 때까지 기다려야 하는 유사한 상황이 발생했습니다.

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

ppss(포기)를 사용해볼 수 있습니다.ppss는 매우 강력하며 미니 클러스터도 만들 수 있습니다.xargs-P는 당황스러울 정도로 병렬 처리를 해야 하는 경우에도 유용할 수 있습니다.

프로세스 산란 관리자

물론, 기술적으로 이것들은 프로세스이고, 이 프로그램은 정말로 프로세스 생성 관리자라고 불릴 수 있지만, 이것은 오직 앰퍼샌드를 사용하여 포크() 또는 아마도 클론() 시스템 호출을 사용하여 별도의 메모리 공간으로 복제할 때 BASH가 작동하는 방식 때문입니다.메모리를 공유하는 pthread_create와 같은 것보다는.BASH가 후자를 지원하는 경우, 각 "실행 시퀀스"는 동일하게 작동하며 더 효율적인 메모리 설치 공간을 확보하면서 기존 스레드라고 할 수 있습니다.기능적으로는 동일하게 작동하지만, 각 작업자 클론에서 글로벌 변수를 사용할 수 없기 때문에 프로세스 간 통신 파일과 기본 플럭 세마포를 사용하여 중요한 섹션을 관리하는 것이 조금 더 어렵습니다.물론 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

어떤 이유에서인지 사용할 수가 없습니다.wait저는 다음과 같은 해결책을 생각해냈습니다.

# create a hashmap of the tasks name -> its command
declare -A tasks=(
  ["Sleep 3 seconds"]="sleep 3"
  ["Check network"]="ping imdb.com"
  ["List dir"]="ls -la"
)

# execute each task in the background, redirecting their output to a custom file descriptor
fd=10
for task in "${!tasks[@]}"; do
    script="${tasks[${task}]}"
    eval "exec $fd< <(${script} 2>&1 || (echo $task failed with exit code \${?}! && touch tasks_failed))"
    ((fd+=1))
done

# print the outputs of the tasks and wait for them to finish
fd=10
for task in "${!tasks[@]}"; do
    cat <&$fd
    ((fd+=1))
done

# determine the exit status
#   by checking whether the file "tasks_failed" has been created
if [ -e tasks_failed ]; then
    echo "Task(s) failed!"
    exit 1
else
    echo "All tasks finished without an error!"
    exit 0
fi

스크립트는 다음과 같아야 합니다.

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

시스템이 한 번에 작업을 수행할 수 있다고 가정합니다.wait를 사용하여 한 번에 n개의 작업만 실행합니다.

다음과 같은 경우:

  • 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 스크린샷 여러 탭 결과

GUI 터미널이 있는 경우 병렬로 실행할 각 프로세스에 대해 새 탭 터미널 인스턴스를 생성할 수 있습니다.

이렇게 하면 각 프로그램이 실행 중인 다른 프로그램과 독립적으로 상호 작용하고 관리할 수 있는 자체 탭에서 실행된다는 이점이 있습니다.

예를 들어 Ubuntu 20.04의 경우:

gnome-terminal --tab -- bash -c 'prog1'
gnome-terminal --tab -- bash -c 'prog2'

특정 프로그램 또는 기타 명령을 순차적으로 실행하려면 다음을 추가할 수 있습니다.;

gnome-terminal --tab -- bash -c 'prog1_1; prog1_2'
gnome-terminal --tab -- bash -c 'prog2'

일부 프로그램의 경우 시작하기 전에 터미널이 닫힙니다.이러한 프로그램의 경우 terminal 명령을 추가합니다.; wait또는; sleep 1

gnome-terminal --tab -- bash -c 'prog1; wait'

Mac OS의 경우 사용 중인 단말기에 해당하는 명령을 찾아야 합니다. Mac이 없기 때문에 Mac OS에서 테스트한 적이 없습니다.

여기에는 많은 흥미로운 답변들이 있습니다. 하지만 저는 이 답변에서 영감을 얻어 여러 프로세스를 병렬로 실행하고 결과를 처리하는 간단한 스크립트를 작성했습니다.요지 또는 아래에서 확인할 수 있습니다.

#!/usr/bin/env bash

# inspired by https://stackoverflow.com/a/29535256/2860309

pids=""
failures=0

function my_process() {
    seconds_to_sleep=$1
    exit_code=$2
    sleep "$seconds_to_sleep"
    return "$exit_code"
}

(my_process 1 0) &
pid=$!
pids+=" ${pid}"
echo "${pid}: 1 second to success"

(my_process 1 1) &
pid=$!
pids+=" ${pid}"
echo "${pid}: 1 second to failure"

(my_process 2 0) &
pid=$!
pids+=" ${pid}"
echo "${pid}: 2 seconds to success"

(my_process 2 1) &
pid=$!
pids+=" ${pid}"
echo "${pid}: 2 seconds to failure"

echo "..."

for pid in $pids; do
        if wait "$pid"; then
                echo "Process $pid succeeded"
        else
                echo "Process $pid failed"
                failures=$((failures+1))
        fi
done

echo
echo "${failures} failures detected"

그 결과:

86400: 1 second to success
86401: 1 second to failure
86402: 2 seconds to success
86404: 2 seconds to failure
...
Process 86400 succeeded
Process 86401 failed
Process 86402 succeeded
Process 86404 failed

2 failures detected

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

언급URL : https://stackoverflow.com/questions/3004811/how-do-you-run-multiple-programs-in-parallel-from-a-bash-script

반응형