bash - then - shell script linux if condition
Paralelamente el script Bash con la cantidad máxima de procesos (15)
Digamos que tengo un bucle en Bash:
for foo in `some-command`
do
do-something $foo
done
do-something
está atado a la CPU y tengo un bonito y brillante procesador de 4 núcleos. Me gustaría poder ejecutar hasta 4 do-something
para do-something
a la vez.
El enfoque ingenuo parece ser:
for foo in `some-command`
do
do-something $foo &
done
Esto ejecutará todos los " do-something
a la vez, pero hay un par de inconvenientes, principalmente que "do-something" también puede tener algunas E / S significativas, que al realizarlas todas a la vez podrían ralentizarse un poco. El otro problema es que este bloque de código regresa inmediatamente, por lo que no hay forma de hacer otro trabajo cuando se completan todas las do-something
.
¿Cómo escribirías este ciclo para que siempre haya X do-something
s funcionando a la vez?
$ DOMAINS = "lista de algunos dominios en comandos" para foo en some-command
do
eval `some-command for $DOMAINS` &
job[$i]=$!
i=$(( i + 1))
hecho
Ndomains = echo $DOMAINS |wc -w
para i en $ (seq 1 1 $ Ndomains) echo eco "espera $ {job [$ i]}" wait "$ {job [$ i]}" done
en este concepto funcionará para la paralelización. Lo importante es que la última línea de eval es ''&'', que colocará los comandos en los fondos.
Aquí hay una solución alternativa que puede insertarse en .bashrc y usarse para un trazador de líneas diario:
function pwait() {
while [ $(jobs -p | wc -l) -ge $1 ]; do
sleep 1
done
}
Para usarlo, todo lo que uno tiene que hacer es poner &
después de los trabajos y una llamada pwait, el parámetro da la cantidad de procesos paralelos:
for i in *; do
do_something $i &
pwait 10
done
Sería mejor usar wait
lugar de busy esperando en la salida de jobs -p
, pero no parece haber una solución obvia para esperar hasta que se termine cualquiera de los trabajos dados en lugar de todos.
Así es como logré resolver este problema en un script bash:
#! /bin/bash
MAX_JOBS=32
FILE_LIST=($(cat ${1}))
echo Length ${#FILE_LIST[@]}
for ((INDEX=0; INDEX < ${#FILE_LIST[@]}; INDEX=$((${INDEX}+${MAX_JOBS})) ));
do
JOBS_RUNNING=0
while ((JOBS_RUNNING < MAX_JOBS))
do
I=$((${INDEX}+${JOBS_RUNNING}))
FILE=${FILE_LIST[${I}]}
if [ "$FILE" != "" ];then
echo $JOBS_RUNNING $FILE
./M22Checker ${FILE} &
else
echo $JOBS_RUNNING NULL &
fi
JOBS_RUNNING=$((JOBS_RUNNING+1))
done
wait
done
Con GNU Parallel http://www.gnu.org/software/parallel/ puedes escribir:
some-command | parallel do-something
GNU Parallel también admite ejecutar trabajos en computadoras remotas. Esto ejecutará uno por núcleo de CPU en las computadoras remotas, incluso si tienen diferente número de núcleos:
some-command | parallel -S server1,server2 do-something
Un ejemplo más avanzado: aquí enumeramos los archivos en los que queremos que se ejecute my_script. Los archivos tienen extensión (tal vez .jpeg). Queremos que la salida de my_script se coloque junto a los archivos en basename.out (por ejemplo, foo.jpeg -> foo.out). Queremos ejecutar my_script una vez para cada núcleo que tenga la computadora y también queremos ejecutarlo en la computadora local. Para las computadoras remotas queremos que el archivo sea procesado y transferido a la computadora dada. Cuando finalice my_script, queremos que foo.out se transfiera nuevamente y luego queremos que foo.jpeg y foo.out sean eliminados de la computadora remota:
cat list_of_files | /
parallel --trc {.}.out -S server1,server2,: /
"my_script {} > {.}.out"
GNU Parallel se asegura de que la salida de cada trabajo no se mezcle, por lo que puede usar el resultado como entrada para otro programa:
some-command | parallel do-something | postprocess
Vea los videos para más ejemplos: https://www.youtube.com/playlist?list=PL284C9FF2488BC6D1
Dependiendo de lo que quiera hacer, xargs también puede ayudar (aquí: convertir documentos con pdf2ps):
cpus=$( ls -d /sys/devices/system/cpu/cpu[[:digit:]]* | wc -w )
find . -name /*.pdf | xargs --max-args=1 --max-procs=$cpus pdf2ps
De los documentos:
--max-procs=max-procs
-P max-procs
Run up to max-procs processes at a time; the default is 1.
If max-procs is 0, xargs will run as many processes as possible at a
time. Use the -n option with -P; otherwise chances are that only one
exec will be done.
El proyecto en el que trabajo utiliza el comando de espera para controlar procesos de shell paralelo (ksh en realidad). Para abordar sus preocupaciones sobre IO, en un SO moderno, es posible que la ejecución en paralelo aumente la eficiencia. Si todos los procesos leen los mismos bloques en el disco, solo el primer proceso tendrá que golpear el hardware físico. Los otros procesos a menudo podrán recuperar el bloque de la memoria caché de disco del sistema operativo. Obviamente, leer de memoria es varios órdenes de magnitud más rápido que leer desde el disco. Además, el beneficio no requiere cambios de codificación.
En lugar de un simple bash, use un Makefile, luego especifique el número de trabajos simultáneos con make -jX
donde X es la cantidad de trabajos que se ejecutarán a la vez.
O puede usar wait
(" man wait
"): inicie varios procesos secundarios, llame a wait
: saldrá cuando termine el proceso hijo.
maxjobs = 10
foreach line in `cat file.txt` {
jobsrunning = 0
while jobsrunning < maxjobs {
do job &
jobsrunning += 1
}
wait
}
job ( ){
...
}
Si necesita almacenar el resultado del trabajo, asigne su resultado a una variable. Después de wait
, solo verifica qué contiene la variable.
Esto podría ser lo suficientemente bueno para la mayoría de los propósitos, pero no es óptimo.
#!/bin/bash
n=0
maxjobs=10
for i in *.m4a ; do
# ( DO SOMETHING ) &
# limit jobs
if (( $(($((++n)) % $maxjobs)) == 0 )) ; then
wait # wait until all have finished (not optimal, but most times good enough)
echo $n wait
fi
done
Mi solución para mantener siempre un número determinado de procesos en ejecución, seguir el seguimiento de errores y manejar procesos ubnterruptible / zombie:
function log {
echo "$1"
}
# Take a list of commands to run, runs them sequentially with numberOfProcesses commands simultaneously runs
# Returns the number of non zero exit codes from commands
function ParallelExec {
local numberOfProcesses="${1}" # Number of simultaneous commands to run
local commandsArg="${2}" # Semi-colon separated list of commands
local pid
local runningPids=0
local counter=0
local commandsArray
local pidsArray
local newPidsArray
local retval
local retvalAll=0
local pidState
local commandsArrayPid
IFS='';'' read -r -a commandsArray <<< "$commandsArg"
log "Runnning ${#commandsArray[@]} commands in $numberOfProcesses simultaneous processes."
while [ $counter -lt "${#commandsArray[@]}" ] || [ ${#pidsArray[@]} -gt 0 ]; do
while [ $counter -lt "${#commandsArray[@]}" ] && [ ${#pidsArray[@]} -lt $numberOfProcesses ]; do
log "Running command [${commandsArray[$counter]}]."
eval "${commandsArray[$counter]}" &
pid=$!
pidsArray+=($pid)
commandsArrayPid[$pid]="${commandsArray[$counter]}"
counter=$((counter+1))
done
newPidsArray=()
for pid in "${pidsArray[@]}"; do
# Handle uninterruptible sleep state or zombies by ommiting them from running process array (How to kill that is already dead ? :)
if kill -0 $pid > /dev/null 2>&1; then
pidState=$(ps -p$pid -o state= 2 > /dev/null)
if [ "$pidState" != "D" ] && [ "$pidState" != "Z" ]; then
newPidsArray+=($pid)
fi
else
# pid is dead, get it''s exit code from wait command
wait $pid
retval=$?
if [ $retval -ne 0 ]; then
log "Command [${commandsArrayPid[$pid]}] failed with exit code [$retval]."
retvalAll=$((retvalAll+1))
fi
fi
done
pidsArray=("${newPidsArray[@]}")
# Add a trivial sleep time so bash won''t eat all CPU
sleep .05
done
return $retvalAll
}
Uso:
cmds="du -csh /var;du -csh /tmp;sleep 3;du -csh /root;sleep 10; du -csh /home"
# Execute 2 processes at a time
ParallelExec 2 "$cmds"
# Execute 4 processes at a time
ParallelExec 4 "$cmds"
Puede usar un bucle for anidado simple (sustituya los enteros apropiados para N y M a continuación):
for i in {1..N}; do
(for j in {1..M}; do do_something; done & );
done
Esto ejecutará do_algo N * M veces en M rondas, cada ronda ejecuta N trabajos en paralelo. Puedes hacer que N sea igual al número de CPU que tienes.
Si bien hacer esto bien en bash
es probablemente imposible, puedes hacer un semi-derecha con bastante facilidad. bstark
dio una buena aproximación de la derecha, pero la suya tiene los siguientes defectos:
- División de palabras: no puede pasarle ningún trabajo que use ninguno de los siguientes caracteres en sus argumentos: espacios, pestañas, líneas nuevas, estrellas, signos de interrogación. Si lo haces, las cosas se romperán, posiblemente de forma inesperada.
- Se basa en el resto de su secuencia de comandos para no hacer un fondo de nada. Si lo hace, o más tarde agrega algo a la secuencia de comandos que se envía en segundo plano porque se olvidó de que no le permitieron usar trabajos con antecedentes debido a su fragmento, las cosas se romperán.
Otra aproximación que no tiene estos defectos es la siguiente:
scheduleAll() {
local job i=0 max=4 pids=()
for job; do
(( ++i % max == 0 )) && {
wait "${pids[@]}"
pids=()
}
bash -c "$job" & pids+=("$!")
done
wait "${pids[@]}"
}
Tenga en cuenta que este es fácilmente adaptable para verificar también el código de salida de cada trabajo, ya que puede avisar al usuario si falla un trabajo o establecer un código de salida para scheduleAll
acuerdo con la cantidad de trabajos que fallaron, o algo así.
El problema con este código es solo eso:
- Programa cuatro trabajos (en este caso) a la vez y luego espera que finalicen los cuatro. Algunos se pueden hacer antes que otros, lo que hará que el siguiente lote de cuatro trabajos espere hasta que se complete el lote más largo del lote anterior.
Una solución que se ocupa de este último problema debería usar kill -0
para sondear si alguno de los procesos ha desaparecido en lugar de wait
y programar el próximo trabajo. Sin embargo, eso introduce un pequeño problema nuevo: tiene una condición de carrera entre el final de un trabajo y el kill -0
comprueba si ha finalizado. Si el trabajo finaliza y otro proceso en su sistema se inicia al mismo tiempo, tomando un PID aleatorio que resulta ser el del trabajo que acaba de finalizar, kill -0
no notará que su trabajo ha terminado y las cosas se romperán de nuevo. .
Una solución perfecta no es posible en bash
.
Si está familiarizado con el comando make
, la mayoría de las veces puede expresar la lista de comandos que desea ejecutar como un archivo MAKE. Por ejemplo, si necesita ejecutar $ SOME_COMMAND en archivos * .input cada uno de los cuales produce * .output, puede usar el archivo make
INPUT = a.input b.input OUTPUT = $(INPUT:.input=.output) %.output : %.input $(SOME_COMMAND) $< $@ all: $(OUTPUT)
y luego solo corre
make -j<NUMBER>
para ejecutar como máximo NUMBER comandos en paralelo.
Tal vez intente una utilidad de paralelización en lugar de reescribir el bucle? Soy un gran fan de xjobs. Uso xjobs todo el tiempo para copiar archivos en masa a través de nuestra red, generalmente al configurar un nuevo servidor de base de datos. http://www.maier-komor.de/xjobs.html
función para bash:
parallel ()
{
awk "BEGIN{print /"all: ALL_TARGETS//n/"}{print /"TARGET_/"NR/"://n//t@-/"/$0/"//n/"}END{printf /"ALL_TARGETS:/";for(i=1;i<=NR;i++){printf /" TARGET_%d/",i};print/"//n/"}" | make $@ -f - all
}
utilizando:
cat my_commands | parallel -j 4
maxjobs=4 parallelize () { while [ $# -gt 0 ] ; do jobcnt=(`jobs -p`) if [ ${#jobcnt[@]} -lt $maxjobs ] ; then do-something $1 & shift else sleep 1 fi done wait } parallelize arg1 arg2 "5 args to third job" arg4 ...