sentencias script programas programacion ejemplos control comandos comando bash process wait

bash - programas - sentencias de control en shell script



¿Cómo esperar en bash varios subprocesos para finalizar y devolver el código de salida!=0 cuando cualquier subproceso termina con el código!=0? (27)

¿Cómo esperar en un script de bash varios subprocesos generados a partir de ese script para finalizar y devolver el código de salida! = 0 cuando cualquiera de los subprocesos termina con el código! = 0?

Guión simple:

#!/bin/bash for i in `seq 0 9`; do doCalculations $i & done wait

La secuencia de comandos anterior esperará a los 10 subprocesos generados, pero siempre dará el estado de salida 0 (ver help wait ). ¿Cómo puedo modificar este script para que descubra los estados de salida de los subprocesos generados y devuelva el código de salida 1 cuando cualquiera de los subprocesos termina con el código! = 0?

¿Hay alguna solución mejor para eso que recolectar los PID de los subprocesos, esperarlos en orden y sumar los estados de salida?


¿Qué tal simplemente?

#!/bin/bash pids="" for i in `seq 0 9`; do doCalculations $i & pids="$pids $!" done wait $pids ...code continued here ...

Actualizar:

Como lo señalaron varios comentaristas, lo anterior espera a que se completen todos los procesos antes de continuar, pero no sale y falla si uno de ellos falla, se puede hacer que se haga con la siguiente modificación sugerida por @Bryan, @SamBrightman y otros :

#!/bin/bash pids="" RESULT=0 for i in `seq 0 9`; do doCalculations $i & pids="$pids $!" done for pid in $pids; do wait $pid || let "RESULT=1" done if [ "$RESULT" == "1" ]; then exit 1 fi ...code continued here ...


Acabo de modificar un script para poner en segundo plano y paralelizar un proceso.

Hice algunos experimentos (en Solaris con bash y ksh) y descubrí que ''esperar'' genera el estado de salida si no es cero, o una lista de trabajos que devuelven una salida distinta de cero cuando no se proporciona un argumento PID. P.ej

Golpetazo:

$ sleep 20 && exit 1 & $ sleep 10 && exit 2 & $ wait [1]- Exit 2 sleep 20 && exit 2 [2]+ Exit 1 sleep 10 && exit 1

Ksh:

$ sleep 20 && exit 1 & $ sleep 10 && exit 2 & $ wait [1]+ Done(2) sleep 20 && exit 2 [2]+ Done(1) sleep 10 && exit 1

Esta salida se escribe en stderr, por lo que una solución simple para el ejemplo de OP podría ser:

#!/bin/bash trap "rm -f /tmp/x.$$" EXIT for i in `seq 0 9`; do doCalculations $i & done wait 2> /tmp/x.$$ if [ `wc -l /tmp/x.$$` -gt 0 ] ; then exit 1 fi

Mientras esto:

wait 2> >(wc -l)

También devolverá un conteo pero sin el archivo tmp. Esto también podría ser usado de esta manera, por ejemplo:

wait 2> >(if [ `wc -l` -gt 0 ] ; then echo "ERROR"; fi)

Pero esto no es mucho más útil que el archivo tmp IMO. No pude encontrar una manera útil de evitar el archivo tmp y, al mismo tiempo, evitar ejecutar la "espera" en un subshell, que no funciona en absoluto.


Aquí está el ejemplo simple usando la wait .

Ejecutar algunos procesos:

$ sleep 10 & $ sleep 10 & $ sleep 20 & $ sleep 20 &

Luego espere con el comando de wait :

$ wait < <(jobs -p)

O simplemente wait (sin argumentos) a todos.

Esto esperará a que se completen todos los trabajos en segundo plano.

Si se proporciona la opción -n , espera a que finalice el siguiente trabajo y devuelve su estado de salida.

Ver: help wait y help jobs para la sintaxis.

Sin embargo, el inconveniente es que esto devolverá solo el estado de la última ID, por lo que debe verificar el estado de cada subproceso y almacenarlo en la variable.

O haga su función de cálculo para crear algún archivo en caso de falla (vacío o con registro de falla), luego verifique ese archivo si existe, por ejemplo

$ sleep 20 && true || tee fail & $ sleep 20 && false || tee fail & $ wait < <(jobs -p) $ test -f fail && echo Calculation failed.


Aquí está mi versión que funciona para varios pids, registra advertencias si la ejecución demora demasiado y detiene los subprocesos si la ejecución demora más que un valor determinado.

function WaitForTaskCompletion { local pids="${1}" # pids to wait for, separated by semi-colon local soft_max_time="${2}" # If execution takes longer than $soft_max_time seconds, will log a warning, unless $soft_max_time equals 0. local hard_max_time="${3}" # If execution takes longer than $hard_max_time seconds, will stop execution, unless $hard_max_time equals 0. local caller_name="${4}" # Who called this function local exit_on_error="${5:-false}" # Should the function exit program on subprocess errors Logger "${FUNCNAME[0]} called by [$caller_name]." local soft_alert=0 # Does a soft alert need to be triggered, if yes, send an alert once local log_ttime=0 # local time instance for comparaison local seconds_begin=$SECONDS # Seconds since the beginning of the script local exec_time=0 # Seconds since the beginning of this function local retval=0 # return value of monitored pid process local errorcount=0 # Number of pids that finished with errors local pidCount # number of given pids IFS='';'' read -a pidsArray <<< "$pids" pidCount=${#pidsArray[@]} while [ ${#pidsArray[@]} -gt 0 ]; do newPidsArray=() for pid in "${pidsArray[@]}"; do if kill -0 $pid > /dev/null 2>&1; then newPidsArray+=($pid) else wait $pid result=$? if [ $result -ne 0 ]; then errorcount=$((errorcount+1)) Logger "${FUNCNAME[0]} called by [$caller_name] finished monitoring [$pid] with exitcode [$result]." fi fi done ## Log a standby message every hour exec_time=$(($SECONDS - $seconds_begin)) if [ $((($exec_time + 1) % 3600)) -eq 0 ]; then if [ $log_ttime -ne $exec_time ]; then log_ttime=$exec_time Logger "Current tasks still running with pids [${pidsArray[@]}]." fi fi if [ $exec_time -gt $soft_max_time ]; then if [ $soft_alert -eq 0 ] && [ $soft_max_time -ne 0 ]; then Logger "Max soft execution time exceeded for task [$caller_name] with pids [${pidsArray[@]}]." soft_alert=1 SendAlert fi if [ $exec_time -gt $hard_max_time ] && [ $hard_max_time -ne 0 ]; then Logger "Max hard execution time exceeded for task [$caller_name] with pids [${pidsArray[@]}]. Stopping task execution." kill -SIGTERM $pid if [ $? == 0 ]; then Logger "Task stopped successfully" else errrorcount=$((errorcount+1)) fi fi fi pidsArray=("${newPidsArray[@]}") sleep 1 done Logger "${FUNCNAME[0]} ended for [$caller_name] using [$pidCount] subprocesses with [$errorcount] errors." if [ $exit_on_error == true ] && [ $errorcount -gt 0 ]; then Logger "Stopping execution." exit 1337 else return $errorcount fi } # Just a plain stupid logging function to replace with yours function Logger { local value="${1}" echo $value }

Por ejemplo, espere a que terminen los tres procesos, registre una advertencia si la ejecución demora más de 5 segundos, detenga todos los procesos si la ejecución demora más de 120 segundos. No salga del programa en caso de fallas.

function something { sleep 10 & pids="$!" sleep 12 & pids="$pids;$!" sleep 9 & pids="$pids;$!" WaitForTaskCompletion $pids 5 120 ${FUNCNAME[0]} false } # Launch the function someting


Creo que la forma más sencilla de ejecutar trabajos en paralelo y verificar el estado es usar archivos temporales. Ya hay un par de respuestas similares (por ejemplo, Nietzche-jou y mug896).

#!/bin/bash rm -f fail for i in `seq 0 9`; do doCalculations $i || touch fail & done wait ! [ -f fail ]

El código anterior no es seguro para subprocesos. Si le preocupa que el código anterior se ejecute al mismo tiempo que el mismo, es mejor usar un nombre de archivo más exclusivo, como fail. $$. La última línea es para cumplir el requisito: "devolver el código de salida 1 cuando cualquiera de los subprocesos termina con el código! = 0?" Tiré un requisito adicional allí para limpiar. Puede haber sido más claro escribirlo así:

#!/bin/bash trap ''rm -f fail.$$'' EXIT for i in `seq 0 9`; do doCalculations $i || touch fail.$$ & done wait ! [ -f fail.$$ ]

Aquí hay un fragmento similar para recopilar resultados de varios trabajos: creo un directorio temporal, cuento los resultados de todas las sub tareas en un archivo separado y luego las vuelco para revisarlas. Esto realmente no coincide con la pregunta, lo estoy lanzando como un bono:

#!/bin/bash trap ''rm -fr $WORK'' EXIT WORK=/tmp/$$.work mkdir -p $WORK cd $WORK for i in `seq 0 9`; do doCalculations $i >$i.result & done wait grep $ * # display the results with filenames and contents


El siguiente código esperará a que se completen todos los cálculos y devolverá el estado de salida 1 si falla alguno de los cálculos de DoCalculations .

#!/bin/bash for i in $(seq 0 9); do (doCalculations $i >&2 & wait %1; echo $?) & done | grep -qv 0 && exit 1


Es posible que la señal de captura de CHLD no funcione porque puede perder algunas señales si llegan simultáneamente.

#!/bin/bash trap ''rm -f $tmpfile'' EXIT tmpfile=$(mktemp) doCalculations() { echo start job $i... sleep $((RANDOM % 5)) echo ...end job $i exit $((RANDOM % 10)) } number_of_jobs=10 for i in $( seq 1 $number_of_jobs ) do ( trap "echo job$i : exit value : /$? >> $tmpfile" EXIT; doCalculations ) & done wait i=0 while read res; do echo "$res" let i++ done < "$tmpfile" echo $i jobs done !!!


Esto es lo que he encontrado hasta ahora. Me gustaría ver cómo interrumpir el comando de suspensión si un niño termina, para que uno no tenga que ajustar WAITALL_DELAY al uso de uno.

waitall() { # PID... ## Wait for children to exit and indicate whether all exited with 0 status. local errors=0 while :; do debug "Processes remaining: $*" for pid in "$@"; do shift if kill -0 "$pid" 2>/dev/null; then debug "$pid is still alive." set -- "$@" "$pid" elif wait "$pid"; then debug "$pid exited with zero exit status." else debug "$pid exited with non-zero exit status." ((++errors)) fi done (("$#" > 0)) || break # TODO: how to interrupt this sleep when a child terminates? sleep ${WAITALL_DELAY:-1} done ((errors == 0)) } debug() { echo "DEBUG: $*" >&2; } pids="" for t in 3 5 4; do sleep "$t" & pids="$pids $!" done waitall $pids


Esto funciona, ¡debería ser tan bueno, si no mejor, que la respuesta de @HoverHell!

#!/usr/bin/env bash set -m # allow for job control EXIT_CODE=0; # exit code of overall script function foo() { echo "CHLD exit code is $1" echo "CHLD pid is $2" echo $(jobs -l) for job in `jobs -p`; do echo "PID => ${job}" wait ${job} || echo "At least one test failed with exit code => $?" ; EXIT_CODE=1 done } trap ''foo $? $$'' CHLD DIRN=$(dirname "$0"); commands=( "{ echo "foo" && exit 4; }" "{ echo "bar" && exit 3; }" "{ echo "baz" && exit 5; }" ) clen=`expr "${#commands[@]}" - 1` # get length of commands - 1 for i in `seq 0 "$clen"`; do (echo "${commands[$i]}" | bash) & # run the command via bash in subshell echo "$i ith command has been issued as a background job" done # wait for all to finish wait; echo "EXIT_CODE => $EXIT_CODE" exit "$EXIT_CODE" # end

y, por supuesto, he inmortalizado este script, en un proyecto NPM que le permite ejecutar comandos de bash en paralelo, útil para probar:

https://github.com/ORESoftware/generic-subshell


Estoy pensando que tal vez ejecute doCalculations; echo "$?" >> / tmp / acc en una subshell que se envía al fondo, luego la espera, luego / tmp / acc contendría los estados de salida, uno por línea. Sin embargo, no conozco las consecuencias de los múltiples procesos que se agregan al archivo del acumulador.

Aquí hay una prueba de esta sugerencia:

Archivo: doCalcualtions

#!/bin/sh random -e 20 sleep $? random -e 10

Archivo: intentar

#!/bin/sh rm /tmp/acc for i in $( seq 0 20 ) do ( ./doCalculations "$i"; echo "$?" >>/tmp/acc ) & done wait cat /tmp/acc | fmt rm /tmp/acc

Salida de ejecución ./try

5 1 9 6 8 1 2 0 9 6 5 9 6 0 0 4 9 5 5 9 8


He intentado esto y he combinado todas las mejores partes de los otros ejemplos aquí. Esta secuencia de comandos ejecutará la función checkpids cuando checkpids cualquier proceso en segundo plano y checkpids el estado de salida sin recurrir a sondeo.

#!/bin/bash set -o monitor sleep 2 & sleep 4 && exit 1 & sleep 6 & pids=`jobs -p` checkpids() { for pid in $pids; do if kill -0 $pid 2>/dev/null; then echo $pid is still alive. elif wait $pid; then echo $pid exited with zero exit status. else echo $pid exited with non-zero exit status. fi done echo } trap checkpids CHLD wait


La solución para esperar varios subprocesos y para salir cuando alguno de ellos sale con un código de estado distinto de cero es mediante el uso de ''wait -n''

#!/bin/bash wait_for_pids() { for (( i = 1; i <= $#; i++ )) do wait -n $@ status=$? echo "received status: "$status if [ $status -ne 0 ] && [ $status -ne 127 ]; then exit 1 fi done } sleep_for_10() { sleep 10 exit 10 } sleep_for_20() { sleep 20 } sleep_for_10 & pid1=$! sleep_for_20 & pid2=$! wait_for_pids $pid2 $pid1

El código de estado ''127'' es para un proceso que no existe, lo que significa que el hijo podría haber salido.


Necesitaba esto, pero el proceso de destino no era un hijo del shell actual, en cuyo caso, la wait $PID no funciona. Encontré la siguiente alternativa en su lugar:

while [ -e /proc/$PID ]; do sleep 0.1 ; done

Eso depende de la presencia de procfs , que puede no estar disponible (Mac no lo proporciona, por ejemplo). Así que para la portabilidad, podrías usar esto en su lugar:

while ps -p $PID >/dev/null ; do sleep 0.1 ; done


No creo que sea posible con la funcionalidad incorporada de Bash.

Puede recibir una notificación cuando un niño sale:

#!/bin/sh set -o monitor # enable script job control trap ''echo "child died"'' CHLD

Sin embargo, no hay una forma aparente de obtener el estado de salida del niño en el controlador de señales.

Obtener ese estado secundario suele ser el trabajo de la familia de funciones de wait en las API POSIX de nivel inferior. Desafortunadamente, el soporte de Bash para eso es limitado: puede esperar un proceso secundario específico (y obtener su estado de salida) o puede esperar a todos y obtener siempre un resultado de 0.

Lo que parece imposible de hacer es el equivalente de waitpid(-1) , que bloquea hasta que se devuelve cualquier proceso secundario.


Para paralelizar esto ...

for i in $(whatever_list) ; do do_something $i done

Traducir a esto ...

for i in $(whatever_list) ; do echo $i ; done | ## execute in parallel... ( export -f do_something ## export functions (if needed) export PATH ## export any variables that are required xargs -I{} --max-procs 0 bash -c '' ## process in batches... { echo "processing {}" ## optional do_something {} }'' )

  • Si ocurre un error en un proceso, no interrumpirá los otros procesos, pero resultará en un código de salida distinto de cero de la secuencia en su totalidad .
  • Las funciones y variables de exportación pueden o no ser necesarias, en cualquier caso particular.
  • Puede configurar --max-procs según el paralelismo que desee ( 0 significa "todos a la vez").
  • GNU Parallel ofrece algunas características adicionales cuando se usa en lugar de xargs , pero no siempre se instala de forma predeterminada.
  • El bucle for no es estrictamente necesario en este ejemplo, ya que echo $i es básicamente la regeneración de la salida de $(whatever_list ). Simplemente creo que el uso de la palabra clave for hace que sea un poco más fácil ver lo que está sucediendo.
  • El manejo de cadenas de Bash puede ser confuso: he encontrado que usar comillas simples funciona mejor para envolver scripts no triviales.
  • Puede interrumpir fácilmente toda la operación (usando ^ C o similar), a diferencia del enfoque más directo al paralelismo de Bash .

Aquí hay un ejemplo de trabajo simplificado ...

for i in {0..5} ; do echo $i ; done |xargs -I{} --max-procs 2 bash -c '' { echo sleep {} sleep 2s }''


Puede haber un caso en el que el proceso se complete antes de esperar el proceso. Si desencadenamos la espera de un proceso que ya ha finalizado, generará un error, ya que pid no es un hijo de este shell. Para evitar tales casos, la siguiente función se puede usar para determinar si el proceso está completo o no:

isProcessComplete(){ PID=$1 while [ -e /proc/$PID ] do echo "Process: $PID is still running" sleep 5 done echo "Process $PID has finished" }


Si tiene disponible bash 4.2 o una versión posterior, lo siguiente podría serle útil. Utiliza matrices asociativas para almacenar nombres de tareas y su "código", así como nombres de tareas y sus pids. También incorporé un método simple de limitación de velocidad que podría ser útil si sus tareas consumen mucha CPU o tiempo de E / S y desea limitar la cantidad de tareas concurrentes.

El script inicia todas las tareas en el primer bucle y consume los resultados en el segundo.

Esto es un poco excesivo para los casos simples pero permite cosas bastante lindas. Por ejemplo, uno puede almacenar mensajes de error para cada tarea en otra matriz asociativa e imprimirlos después de que todo se haya calmado.

#! /bin/bash main () { local -A pids=() local -A tasks=([task1]="echo 1" [task2]="echo 2" [task3]="echo 3" [task4]="false" [task5]="echo 5" [task6]="false") local max_concurrent_tasks=2 for key in "${!tasks[@]}"; do while [ $(jobs 2>&1 | grep -c Running) -ge "$max_concurrent_tasks" ]; do sleep 1 # gnu sleep allows floating point here... done ${tasks[$key]} & pids+=(["$key"]="$!") done errors=0 for key in "${!tasks[@]}"; do pid=${pids[$key]} local cur_ret=0 if [ -z "$pid" ]; then echo "No Job ID known for the $key process" # should never happen cur_ret=1 else wait $pid cur_ret=$? fi if [ "$cur_ret" -ne 0 ]; then errors=$(($errors + 1)) echo "$key (${tasks[$key]}) failed." fi done return $errors } main


Si tienes instalado GNU Parallel puedes hacer:

# If doCalculations is a function export -f doCalculations seq 0 9 | parallel doCalculations {}

GNU Parallel te dará el código de salida:

  • 0 - Todos los trabajos se ejecutaron sin error.

  • 1-253 - Algunos de los trabajos fallaron. El estado de salida da el número de trabajos fallidos

  • 254 - Más de 253 trabajos fallaron.

  • 255 - Otro error.

Mire los videos de introducción para obtener más información: http://pi.dk/1


Simplemente almacene los resultados fuera del shell, por ejemplo, en un archivo.

#!/bin/bash tmp=/tmp/results : > $tmp #clean the file for i in `seq 0 9`; do (doCalculations $i; echo $i:$?>>$tmp)& done #iterate wait #wait until all ready sort $tmp | grep -v '':0'' #... handle as required


Utilicé esto recientemente (gracias a Alnitak):

#!/bin/bash # activate child monitoring set -o monitor # locking subprocess (while true; do sleep 0.001; done) & pid=$! # count, and kill when all done c=0 function kill_on_count() { # you could kill on whatever criterion you wish for # I just counted to simulate bash''s wait with no args [ $c -eq 9 ] && kill $pid c=$((c+1)) echo -n ''.'' # async feedback (but you don''t know which one) } trap "kill_on_count" CHLD function save_status() { local i=$1; local rc=$2; # do whatever, and here you know which one stopped # but remember, you''re called from a subshell # so vars have their values at fork time } # care must be taken not to spawn more than one child per loop # e.g don''t use `seq 0 9` here! for i in {0..9}; do (doCalculations $i; save_status $i $?) & done # wait for locking subprocess to be killed wait $pid echo

Desde allí, se puede extrapolar fácilmente y tener un disparador (tocar un archivo, enviar una señal) y cambiar los criterios de conteo (contar los archivos tocados, o lo que sea) para responder a ese disparador. O si solo quieres "cualquier" rc cero, simplemente elimina el bloqueo de save_status.


Veo muchos buenos ejemplos enumerados aquí, también quise incluir los míos.

#! /bin/bash items="1 2 3 4 5 6" pids="" for item in $items; do sleep $item & pids+="$! " done for pid in $pids; do wait $pid if [ $? -eq 0 ]; then echo "SUCCESS - Job $pid exited with a status of $?" else echo "FAILED - Job $pid exited with a status of $?" fi done

Uso algo muy similar para iniciar / detener servidores / servicios en paralelo y comprobar el estado de cada salida. Funciona muy bien para mí. ¡Espero que esto ayude a alguien!


Ya hay muchas respuestas aquí, pero me sorprende que nadie parece haber sugerido el uso de matrices ... Así que esto es lo que hice: esto podría ser útil para algunos en el futuro.

n=10 # run 10 jobs c=0 PIDS=() while true my_function_or_command & PID=$! echo "Launched job as PID=$PID" PIDS+=($PID) (( c+=1 )) # required to prevent any exit due to error # caused by additional commands run which you # may add when modifying this example true do if (( c < n )) then continue else break fi done # collect launched jobs for pid in "${PIDS[@]}" do wait $pid || echo "failed job PID=$pid" done


la trampa es tu amiga Usted puede atrapar en ERR en muchos sistemas. Puede interceptar EXIT o en DEBUG para realizar un fragmento de código después de cada comando.

Esto además de todas las señales estándar.


http://jeremy.zawodny.com/blog/archives/010717.html :

#!/bin/bash FAIL=0 echo "starting" ./sleeper 2 0 & ./sleeper 2 1 & ./sleeper 3 0 & ./sleeper 2 0 & for job in `jobs -p` do echo $job wait $job || let "FAIL+=1" done echo $FAIL if [ "$FAIL" == "0" ]; then echo "YAY!" else echo "FAIL! ($FAIL)" fi


wait también (opcionalmente) toma el PID del proceso a esperar, y con $! obtienes el PID del último comando lanzado en segundo plano. Modifique el bucle para almacenar el PID de cada subproceso generado en una matriz, y luego haga un bucle nuevamente esperando cada PID.

# run processes and store pids in array for i in $n_procs; do ./procs[${i}] & pids[${i}]=$! done # wait for all pids for pid in ${pids[*]}; do wait $pid done


#!/bin/bash set -m for i in `seq 0 9`; do doCalculations $i & done while fg; do true; done

  • set -m te permite usar fg & bg en un script
  • fg , además de poner el último proceso en primer plano, tiene el mismo estado de salida que el proceso en primer plano
  • while fg se detendrá cuando cualquier fg salga con un estado de salida distinto de cero

desafortunadamente esto no manejará el caso cuando un proceso en segundo plano sale con un estado de salida distinto de cero. (el bucle no terminará de inmediato. esperará a que se completen los procesos anteriores).


set -e fail () { touch .failure } expect () { wait if [ -f .failure ]; then rm -f .failure exit 1 fi } sleep 2 || fail & sleep 2 && false || fail & sleep 2 || fail expect

El set -e en la parte superior hace que su script se detenga en caso de error.

expect devolverá 1 si falla alguna sub-tarea.