Como você executa vários programas em paralelo a partir de um script bash?

245

Estou tentando escrever um arquivo .sh que executa muitos programas simultaneamente

Eu tentei isso

prog1 
prog2

Mas isso executa prog1, então espera até que prog1 termine e, em seguida, inicia prog2 ...

Então, como posso executá-los em paralelo?

Betamoo
fonte

Respostas:

216
prog1 &
prog2 &
psmears
fonte
49
Não esqueça o wait! Sim, no bash você pode esperar pelos processos filhos do script.
Dummy00001
5
Outra opção é usar nohuppara impedir que o programa seja morto quando o shell desligar.
Philipp
@liang: Sim, também funcionará com três ou mais programas.
Psmears 19/04/19
302

E se:

prog1 & prog2 && fg

Isso vai:

  1. Iniciar prog1.
  2. Envie-o para segundo plano, mas continue imprimindo sua saída.
  3. Comece prog2e mantenha-o em primeiro plano , para que você possa fechá-lo ctrl-c.
  4. Quando você fecha prog2, você vai voltar para prog1's primeiro plano , então você também pode fechá-lo com ctrl-c.
Ory Band
fonte
9
Existe uma maneira fácil de terminar prog1quando prog2termina? Pense em node srv.js & cucumberjs
JP
20
Apenas tentei isso e não funcionou como esperado para mim. No entanto, uma ligeira modificação funcionou: prog1 & prog2 ; fg Isso era para executar vários túneis ssh de uma só vez. Espero que isso ajude alguém.
precisa saber é o seguinte
2
@ jnadro52 sua solução tem o efeito de que, se prog2não funcionar imediatamente, você voltará a ficar prog1em primeiro plano. Se isso é desejável, está tudo bem.
Ory Banda
3
No shell SSH'ed Se você executar um comando como este, será complicado eliminar prog1. Ctrl-c não funcionou para mim. Até matar todo o terminal deixou o prog1 em execução.
mercury0114
14
@ jnadro52 É uma maneira de finalizar os dois processos de uma só vez prog1 & prog2 && kill $!.
Zaboco 25/04
79

Você pode usar wait:

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

Ele atribui os PIDs do programa em segundo plano às variáveis ​​( $!é o último PID do processo iniciado) e o waitcomando as espera. É legal porque se você matar o script, ele também matará os processos!

trusktr
fonte
4
Na minha experiência , matar a espera também não mata os outros processos.
Quinn Comendant 27/08/18
1
Se estou iniciando processos em segundo plano em um loop, como posso esperar que todos os processos em segundo plano sejam concluídos antes de avançar com a execução do próximo conjunto de comandos. #!/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, acho que você pode salvar os IDs do processo em uma matriz e chamar a espera na matriz. Eu acho que você tem que usar ${}para interpolá-lo em uma lista de seqüências de caracteres ou similar.
trusktr
a melhor resposta e, para mim, matar o script também mata os processos! macOS Catalina, console zsh
Michael Klishevich
67

Com o GNU Parallel http://www.gnu.org/software/parallel/ , é tão fácil quanto:

(echo prog1; echo prog2) | parallel

Ou se você preferir:

parallel ::: prog1 prog2

Saber mais:

Ole Tange
fonte
4
Vale a pena notar que existem versões paralleldiferentes com sintaxe diferente. Por exemplo, nos derivativos Debian, o moreutilspacote contém um comando diferente chamado parallelque se comporta de maneira bem diferente.
Joel Cross
4
é parallelmelhor do que usar &?
Optimus Prime
2
@OptimusPrime Realmente depende. O GNU Parallel introduz uma sobrecarga, mas, em troca, oferece muito mais controle sobre os trabalhos e a saída em execução. Se dois trabalhos forem impressos ao mesmo tempo, o GNU Parallel garantirá que a saída não seja misturada.
precisa
1
O @OptimusPrime parallelé melhor quando há mais trabalhos que núcleos; nesse caso, &seria executado mais de um trabalho por núcleo de uma só vez. (cf. princípio pigeonhole )
Geremia
2
@OleTange " Sua linha de comando vai te amar por isso. " Eu também. Ger
Geremia
55

Se você deseja executar e matar com facilidade vários processos com facilidade ctrl-c, este é o meu método favorito: gerar vários processos em segundo plano em um (…)subshell e fazer uma armadilha SIGINTpara executar kill 0, o que matará tudo o que for gerado no grupo de subshells:

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

Você pode ter estruturas complexas de execução de processos, e tudo será fechado com uma única ctrl-c(apenas verifique se o último processo é executado em primeiro plano, ou seja, não inclua um &depois prog1.3):

(trap 'kill 0' SIGINT; prog1.1 && prog1.2 & (prog2.1 | prog2.2 || prog2.3) & prog1.3)
Quinn Comendant
fonte
Esta é a melhor resposta de longe.
Nic
10

xargs -P <n>permite executar <n>comandos em paralelo.

Embora -Pseja uma opção não padrão, as implementações GNU (Linux) e macOS / BSD a suportam.

O exemplo a seguir:

  • executa no máximo 3 comandos em paralelo por vez,
  • com comandos adicionais iniciando apenas quando um processo iniciado anteriormente termina.
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

A saída se parece com:

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

O tempo mostra que os comandos foram executados em paralelo (o último comando foi iniciado somente após o término do primeiro dos 3 originais, mas executado muito rapidamente).

O xargscomando em si não retornará até que todos os comandos tenham sido concluídos, mas você pode executá-lo em segundo plano encerrando-o com o operador de controle &e, em seguida, usando o waitbuiltin para aguardar xargsa conclusão de todo o comando.

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

Nota:

  • O BSD / macOS xargsexige que você especifique a contagem de comandos a serem executados em paralelo explicitamente , enquanto o GNU xargspermite especificar -P 0a execução do maior número possível em paralelo.

  • A saída dos processos executados em paralelo chega à medida que está sendo gerada , portanto será imprevisivelmente intercalada .

    • O GNU parallel, como mencionado na resposta de Ole (não é padrão na maioria das plataformas), serializa (agrupa) convenientemente a saída por processo e oferece muitos recursos mais avançados.
mklement0
fonte
9
#!/bin/bash
prog1 & 2> .errorprog1.log; prog2 & 2> .errorprog2.log

Redirecionar erros para separar logs.

fermin
fonte
13
Você deve colocar oe comercial após os redirecionamentos e deixar o ponto-e-vírgula (o e comercial também executará a função de um separador de comando):prog1 2> .errorprog1.log & prog2 2> .errorprog2.log &
Pausado até novo aviso.
o ponto e vírgula executa os dois comandos, você pode testar o bash para ver se funciona bem;) Exemplo: pwd & 2> .errorprog1.log; echo "wop" & 2> .errorprog2.log quando você coloca o programa em segundo plano e executa imediatamente o próximo comando.
fermin
2
Não funciona - os erros não são redirecionados para o arquivo. Tente com: ls notthere1 & 2> .errorprog1.log; ls notthere2 & 2>.errorprog2.log. Os erros vão para o console e os dois arquivos de erro estão vazios. Como @Dennis Williamson diz, &é um separador, assim ;, (a) ele precisa ir no final do comando (após qualquer redirecionamento) e (b) você não precisa de ;nada :-)
psmears
8

Existe um programa muito útil que chama nohup.

     nohup - run a command immune to hangups, with output to a non-tty
3h4x
fonte
4
nohuppor si só não executa nada em segundo plano, e o uso nohupnão é um requisito ou pré-requisito para executar tarefas em segundo plano. Eles geralmente são úteis juntos, mas, como tal, isso não responde à pergunta.
Tripleee
8

Aqui está uma função que eu uso para executar no máximo n processo em paralelo (n = 4 no exemplo):

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

Se max_children estiver definido como o número de núcleos, essa função tentará evitar núcleos inativos.

arnaldocan
fonte
1
Bom trecho, mas não consigo encontrar a explicação de "wait-n" no meu bash, mas diz que é uma opção inválida. erro de digitação ou eu perdi alguma coisa?
Emmanuel Devaux
1
@EmmanuelDevaux: wait -nrequer bash4.3+ e muda a lógica para aguardar o término de qualquer um dos processos especificados / implícitos.
mklement0
e se uma das tarefas falhar, quero encerrar os scripts?
52
@ 52code, você pode ajustar a função para capturar um filho com falha, algo como: "$ @" && time2 = $ (date + "% H:% M:% S") && echo "terminando $ 2 ($ time1 - $ time2 ) ... "|| erro = 1 &. Em seguida, teste para erro no "se" parte e abortar a função se necessário
arnaldocan
7

Você pode tentar ppss . ppss é bastante poderoso - você pode até criar um mini-cluster. O xargs -P também pode ser útil se você tiver um lote de processamento paralelamente embaraçoso para fazer.

ljt
fonte
7

Recentemente, tive uma situação semelhante em que precisava executar vários programas ao mesmo tempo, redirecionar suas saídas para arquivos de log separados e esperar que eles terminassem, e acabei com algo assim:

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

Fonte: http://joaoperibeiro.com/execute-multiple-programs-and-redirect-their-outputs-linux/

Joaopcribeiro
fonte
4

Gerente de Geração de Processos

Certamente, tecnicamente, esses são processos, e esse programa deve realmente ser chamado de gerenciador de criação de processos, mas isso se deve apenas à maneira como o BASH funciona quando bifurca usando o e comercial, usa a chamada de sistema fork () ou talvez clone () que clona em um espaço de memória separado, em vez de algo como pthread_create () que compartilharia memória. Se o BASH suportasse o último, cada "sequência de execução" funcionaria da mesma forma e poderia ser denominada como threads tradicionais, enquanto ganhava uma pegada de memória mais eficiente. No entanto, funcionalmente, funciona da mesma maneira, embora um pouco mais difícil, pois as variáveis ​​GLOBAL não estão disponíveis em cada clone do trabalhador, portanto, o uso do arquivo de comunicação entre processos e o semáforo rudimentar do rebanho para gerenciar seções críticas. Bifurcação do BASH, é claro, é a resposta básica aqui, mas sinto que as pessoas sabem disso, mas realmente querem gerenciar o que é gerado, em vez de apenas bifurcar e esquecê-lo. Isso demonstra uma maneira de gerenciar até 200 instâncias de processos bifurcados, acessando um único recurso. Claramente, isso é um exagero, mas eu gostei de escrever, então continuei. Aumente o tamanho do seu terminal de acordo. Espero que você ache isso útil.

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
Josiah DeWitt
fonte
0

Seu script deve se parecer com:

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

Supondo que seu sistema possa executar n trabalhos por vez. use wait para executar apenas n trabalhos por vez.

amalik2205
fonte
-1

Com o bashj ( https://sourceforge.net/projects/bashj/ ), você deve poder executar não apenas vários processos (da maneira que outros sugeriram), mas também vários Threads em uma JVM controlada a partir do seu script. Mas é claro que isso requer um JDK java. Threads consomem menos recursos que processos.

Aqui está um código de trabalho:

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