script - cómo escribir un shell bash de proceso
scripts bash ejemplos (9)
Tengo más de 10 tareas para ejecutar, y el sistema restringe que, como máximo, se pueden ejecutar 4 tareas al mismo tiempo.
Mi tarea se puede iniciar como: myprog taskname
¿Cómo puedo escribir un script de shell bash para ejecutar estas tareas. Lo más importante es que cuando una tarea termina, la secuencia de comandos puede comenzar otra inmediatamente, haciendo que las tareas en ejecución cuenten con 4 todo el tiempo.
Este script probado ejecuta 5 trabajos a la vez y reiniciará un nuevo trabajo tan pronto como lo haga (debido a la eliminación del sleep 10.9 cuando obtenemos un SIGCHLD. Una versión más simple de esto podría usar el sondeo directo (cambie el sleep 10.9 a Duerme 1 y deshazte de la trampa).
#!/usr/bin/bash
set -o monitor
trap "pkill -P $$ -f ''sleep 10/.9'' >&/dev/null" SIGCHLD
totaljobs=15
numjobs=5
worktime=10
curjobs=0
declare -A pidlist
dojob()
{
slot=$1
time=$(echo "$RANDOM * 10 / 32768" | bc -l)
echo Starting job $slot with args $time
sleep $time &
pidlist[$slot]=`jobs -p %%`
curjobs=$(($curjobs + 1))
totaljobs=$(($totaljobs - 1))
}
# start
while [ $curjobs -lt $numjobs -a $totaljobs -gt 0 ]
do
dojob $curjobs
done
# Poll for jobs to die, restarting while we have them
while [ $totaljobs -gt 0 ]
do
for ((i=0;$i < $curjobs;i++))
do
if ! kill -0 ${pidlist[$i]} >&/dev/null
then
dojob $i
break
fi
done
sleep 10.9 >&/dev/null
done
wait
Me topé con este hilo mientras escribía sobre mi propio grupo de procesos y me gustó especialmente la solución de Brandon Horsley, aunque no pude hacer que las señales funcionaran correctamente, así que me inspiré en Apache y decidí probar un modelo pre-fork con un fifo como mi cola de trabajo
La siguiente función es la función que los procesos de trabajo ejecutan cuando se bifurcan.
# /brief the worker function that is called when we fork off worker processes
# /param[in] id the worker ID
# /param[in] job_queue the fifo to read jobs from
# /param[in] result_log the temporary log file to write exit codes to
function _job_pool_worker()
{
local id=$1
local job_queue=$2
local result_log=$3
local line=
exec 7<> ${job_queue}
while [[ "${line}" != "${job_pool_end_of_jobs}" && -e "${job_queue}" ]]; do
# workers block on the exclusive lock to read the job queue
flock --exclusive 7
read line <${job_queue}
flock --unlock 7
# the worker should exit if it sees the end-of-job marker or run the
# job otherwise and save its exit code to the result log.
if [[ "${line}" == "${job_pool_end_of_jobs}" ]]; then
# write it one more time for the next sibling so that everyone
# will know we are exiting.
echo "${line}" >&7
else
_job_pool_echo "### _job_pool_worker-${id}: ${line}"
# run the job
{ ${line} ; }
# now check the exit code and prepend "ERROR" to the result log entry
# which we will use to count errors and then strip out later.
local result=$?
local status=
if [[ "${result}" != "0" ]]; then
status=ERROR
fi
# now write the error to the log, making sure multiple processes
# don''t trample over each other.
exec 8<> ${result_log}
flock --exclusive 8
echo "${status}job_pool: exited ${result}: ${line}" >> ${result_log}
flock --unlock 8
exec 8>&-
_job_pool_echo "### _job_pool_worker-${id}: exited ${result}: ${line}"
fi
done
exec 7>&-
}
Puedes obtener una copia de mi solución en Github. Aquí hay un programa de ejemplo usando mi implementación.
#!/bin/bash
. job_pool.sh
function foobar()
{
# do something
true
}
# initialize the job pool to allow 3 parallel jobs and echo commands
job_pool_init 3 0
# run jobs
job_pool_run sleep 1
job_pool_run sleep 2
job_pool_run sleep 3
job_pool_run foobar
job_pool_run foobar
job_pool_run /bin/false
# wait until all jobs complete before continuing
job_pool_wait
# more jobs
job_pool_run /bin/false
job_pool_run sleep 1
job_pool_run sleep 2
job_pool_run foobar
# don''t forget to shut down the job pool
job_pool_shutdown
# check the $job_pool_nerrors for the number of jobs that exited non-zero
echo "job_pool_nerrors: ${job_pool_nerrors}"
¡Espero que esto ayude!
Mire mi implementación del grupo de trabajo en bash: https://github.com/spektom/shell-utils/blob/master/jp.sh
Por ejemplo, para ejecutar un máximo de 3 procesos de cURL al descargar desde una gran cantidad de URL, puede ajustar sus comandos de cURL de la siguiente manera:
./jp.sh "My Download Pool" 3 curl http://site1/...
./jp.sh "My Download Pool" 3 curl http://site2/...
./jp.sh "My Download Pool" 3 curl http://site3/...
...
Otra respuesta sobre 4 scripts de shell no me satisface completamente, ya que asume que todas las tareas toman aproximadamente el mismo tiempo y porque requiere una configuración manual. Pero aquí es cómo lo mejoraría.
La secuencia de comandos principal creará enlaces simbólicos a los ejecutables siguiendo cierta convención de namimg. Por ejemplo,
ln -s executable1 ./01-task.01
El primer prefijo es para la clasificación y el sufijo identifica el lote (01-04). Ahora generamos 4 scripts de shell que tomarían el número de lote como entrada y haríamos algo como esto
for t in $(ls ./*-task.$batch | sort ; do
t
rm t
done
Probablemente podrías hacer algo inteligente con señales.
Tenga en cuenta que esto es solo para ilustrar el concepto, y por lo tanto no está probado a fondo.
#!/usr/local/bin/bash
this_pid="$$"
jobs_running=0
sleep_pid=
# Catch alarm signals to adjust the number of running jobs
trap ''decrement_jobs'' SIGALRM
# When a job finishes, decrement the total and kill the sleep process
decrement_jobs()
{
jobs_running=$(($jobs_running - 1))
if [ -n "${sleep_pid}" ]
then
kill -s SIGKILL "${sleep_pid}"
sleep_pid=
fi
}
# Check to see if the max jobs are running, if so sleep until woken
launch_task()
{
if [ ${jobs_running} -gt 3 ]
then
(
while true
do
sleep 999
done
) &
sleep_pid=$!
wait ${sleep_pid}
fi
# Launch the requested task, signalling the parent upon completion
(
"$@"
kill -s SIGALRM "${this_pid}"
) &
jobs_running=$((${jobs_running} + 1))
}
# Launch all of the tasks, this can be in a loop, etc.
launch_task task1
launch_task tast2
...
launch_task task99
Siguiendo la respuesta de @Parag Sardas y la documentación vinculada, aquí hay un script rápido que puede agregar en sus .bash_aliases
.
Volver a vincular el enlace del documento porque vale la pena leerlo.
#!/bin/bash
# https://.com/a/19618159
# https://.com/a/51861820
#
# Example file contents:
# touch /tmp/a.txt
# touch /tmp/b.txt
if [ "$#" -eq 0 ]; then
echo "$0 <file> [max-procs=0]"
exit 1
fi
FILE=${1}
MAX_PROCS=${2:-0}
cat $FILE | while read line; do printf "%q/n" "$line"; done | xargs --max-procs=$MAX_PROCS -I CMD bash -c CMD
Es decir ./xargs-parallel.sh jobs.txt 4
máximo de 4 procesos leídos de jobs.txt
Sugeriría escribir cuatro guiones, cada uno de los cuales ejecuta un cierto número de tareas en serie. Luego escriba otro script que inicie los cuatro scripts en paralelo. Por ejemplo, si tiene scripts, script1.sh, script2.sh, script3.sh y script4.sh, podría tener un script llamado headscript.sh como tal.
#!/bin/sh
./script1.sh &
./script2.sh &
./script3.sh &
./script4.sh &
Usando GNU Parallel puedes hacer:
cat tasks | parallel -j4 myprog
Si tiene 4 núcleos, incluso puede hacer:
cat tasks | parallel myprog
De http://git.savannah.gnu.org/cgit/parallel.git/tree/README :
Instalación completa
La instalación completa de GNU Parallel es tan simple como:
./configure && make && make install
Instalación personal
Si no eres root, puedes agregar ~ / bin a tu ruta e instalarlo en ~ / bin y ~ / share:
./configure --prefix=$HOME && make && make install
O si su sistema carece de ''make'', simplemente puede copiar src / parallel src / sem src / niceload src / sql a un directorio en su ruta.
Instalación mínima
Si solo necesita paralelo y no tiene instalado ''make'' (tal vez el sistema sea antiguo o Microsoft Windows):
wget http://git.savannah.gnu.org/cgit/parallel.git/plain/src/parallel
chmod 755 parallel
cp parallel sem
mv parallel sem dir-in-your-$PATH/bin/
Probar la instalación
Después de esto deberías poder hacer:
parallel -j0 ping -nc 3 ::: foss.org.my gnu.org freenetproject.org
Esto enviará 3 paquetes de ping a 3 hosts diferentes en paralelo e imprimirá la salida cuando se completen.
Mire el video de introducción para obtener una introducción rápida: https://www.youtube.com/playlist?list=PL284C9FF2488BC6D1
Utilice xargs
:
xargs -P <maximun-number-of-process-at-a-time> -n <arguments per process> <commnad>
Detalles here .