shellscript: procesando una lista en paralelo
Gracias a un oportuno comentario de CMA, nace una obra de arte :P.
#!/bin/sh
PLIMIT=5
PCOUNT=0
PIDS=
while read item; do
procesar $item &
PIDS="$PIDS $!"
[ $((++PCOUNT)) -ge $PLIMIT ] && {
while true; do
CUR=`echo $PIDS | cut -f1`
PIDS=`echo $PIDS | cut -f2-`
wait $CUR
[ $((--PCOUNT)) -lt $PLIMIT -o $? -eq 127 ] || break
done
}
done
wait
Comentarios? :)
es un poquito dificil dar una opinion sin el shebang ya que las reglas varian segun el shell pero bueno te recomiendo lo siguiente:
- no uses variables en mayusculas, sucede que bash y sh (shell mas comunes) usan esa convencion para sus propias variables y es facil tener colisiones si usas el mismo estilo.
- agrega comillas dobles a todas las variables, de lo contrario si alguna variable llega a tener espacios te va a dar problemas
- optimiza tus condiciones, [ $((++PCOUNT)) -ge $PLIMIT ] se puede escribir simplemente como ((++PCOUNT > PLIMIT)) y [ $((--PCOUNT)) -lt $PLIMIT -o $? -eq 127 ] es equivalente a ((–PCOUNT < PLIMIT || $? == 127))
- no uses backticks, es prefereible usar $() ya que es mas facil de anidar (de hecho vos los tenes dentro de comillas dobles y solo eso ya es dificil de leer)
- no es necesario usar comillas dobles al asignar, por lo tanto CUR="`echo $PIDS | cut -f1`" se puede poner simplemente como CUR=`echo $PIDS | cut -f1`
- usa redirecciones en lugar de pipes cuando sea posible, sucede que los pipes crean una subshell cada uno, cuando no son necesarios solo producen overhead que se suma a la invocacion del programa que vas a pipear; entonces esto echo $PIDS | cut -f1 se puede escribir simplemente como cut -f1 <<<"$PIDS"
saludos_
Samus_
2009-07-17 at 19:25
- Hablamos de POSIX, porsupuesto.
- Con respecto a las variables en mayúsculas, no veo nada de malo… eso es un tema de estilo, de cada programador, el shell puede o no definir variables pero nada fuera de lo estándar debería modificar el comportamiento del shell o los programas ejecutados.
- Respecto a las comillas, no son necesarias si sabemos que la variable nunca contendrá espacios.
- El problema con (()) y $() es que no son estándar.
- Lo de los backticks es cierto, pero bueno, no hacen nada malo…
- Las redirección <<< no es estándar.
Creo que eso responde todo… no?
Ismael Luceno
2009-07-18 at 00:46
no me esperaba una postura tan defensiva ante mis sugerencias, es mas me parece bastante tonto cerrar un post diciendo “Comentarios?” y contestar asi.
de todas formas como soy el unico que se digno a responder me permito continuar; con respecto a POSIX esta bien, siendo esas las reglas hay ciertas cosas que no se aplican, yo personalmente elijo trabajar directo con bash ya que a mi entender es lo suficientemente estandar. esta presente en todas las distribuciones de linux con las que he tenido contacto y en otros sistemas operativos los problemas de compatibilidad ocurren mas por el resto de las herramientas que por el shell en si.
repasando los puntos planteados, te diria que es entendible que no le veas nada de malo a las variables en mayusculas, es un tema de experiencia pero te lo voy a explicar con otro ejemplo: segun tu planteo tampoco tendria nada de malo utilizar nombres cripticos para las variables (no es que los tuyos sean demasiado descriptivos) asi como tampoco tendrias problemas en no indentar el codigo ni darle una correcta estructura al flujo de tu programa despues de todo “eso es un tema de estilo de cada programador” no? te cuento que esto no es asi o mejor dicho, uno siempre tiene la opcion de hacer las cosas mal pero eso no significa que sea recomendable, he ahi el termino “buenas practicas”.
por otra parte te cuento que tu afirmacion “el shell puede o no definir variables pero nada fuera de lo estándar debería modificar el comportamiento del shell o los programas ejecutados” es incorrecta, bash define ciertas variables especiales que en caso de ser asignadas o ser eliminadas y recreadas pierden sus propiedades, eso definitivamente es un error de tu parte ya que asi esta documentado y es el comportamiento esperado de la herramienta con la cual estas trabajando.
en definitiva, la idea de usar una convencion diferente a la utilizada por el shell es para ahorrarse dolores de cabeza, por supuesto que siempre se puede elejir hacerlo mal, si esa es tu intencion.
el siguiente punto es: “Respecto a las comillas, no son necesarias si sabemos que la variable nunca contendrá espacios.” esto tiene un nombre y se llama negligencia, los unicos casos donde realmente podrias afirmar eso son variables de shell que sean explicitamente numericas (y si tuviste la suerte de redefinirlas gracias a tu convencion de nombres, ni siquiera en esos casos).
es mas, una de tus variables se toma desde una instruccion read, lo cual implica un dato ingresado por el usuario asi que ni aun en tu propio ejemplo es valida tu afirmacion, no discutas lo obvio no tiene caso.
“El problema con (()) y $() es que no son estándar.” ehh http://www.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_06_03 aunque tenes razon con respecto a (()).
“Lo de los backticks es cierto, pero bueno, no hacen nada malo…” de nuevo: buena practica / mala practica pero a diferencia del caso de los esto solo afecta la legibilidad, aca te dejo una discusion al respecto con algunos ejemplos: http://mywiki.wooledge.org/BashFAQ/082 como siempre, es tu opcion hacerlo bien o mal.
“Las redirección <<< no es estándar." esto no lo sabia, aunque no me preocupe por POSIX en el primer comentario sino que lo puse mas de acuerdo a mis costumbres y conocimientos sin ir a verificar a la referencia, de todas formas si bien el here-string no es estandar, si lo es el here-document por lo tanto es valido lo siguiente:
cut -f1 <<heredoc
$PIDS
heredoc
en conclusion Ismael, no es mi intencion que tomes esto como una agresion yo simplemente soy un compatriota que paso de casualidad por aqui y quiso compartir contigo algo de interes y por que no, tambien aprender algo nuevo de tu parte.
saludos_
Samus_
2009-07-20 at 22:59
Jajaja, bueno, no me lo tomé a mal eh XD, nomás soy un poco asocial y me cuesta comunicarme como los humanos normales :P.
OK, ahora me leo lo de $(), me parecía que esa adición no la habían estandarizado, pero mi memoria falla a veces.
Con respecto a los nombres de variables, lo normal es usar mayúsculas para variables globales y minúsculas para variables locales a la función, la razón es que no existen las variables locales en un sh POSIX estricto. La otra opción sería lanzar un subshell por cada función… no muy eficiente, ¿no?
Con respecto a bash, te recuerdo que existen muchos sistemas derivados de BSD también, y que nunca incluirán bash en su instalación mínima. Y con respecto a sus variables, no deben bajo ningún concepto cambiar el comportamiento del shell en lo que respecta a un script 100% estándar, excepto claro que estuviesemos hablando de alguna variable estándar.
Respecto a las variables y las comillas, en este caso, nunca contendrán valores no numéricos, a exepción de $item, pero “procesar $item” es solo un “place-holder” para código real.
Ciertamente “heredoc” serviría en el caso de pasarle la entrada a cut, sin embargo no veo la diferencia con usar echo, excepto que tomaría más líneas. o sea, el item, el delimitador y la línea en blanco obligatoria… y bueno, que en un shell que no tenga echo interno va a tener que ejecutarlo, cosa bastante rara hoy día.
Nomás trato de encontrar algún argumento sólido para cambiarlo, si no, se quedará así… bueno, y ya retiro las comillas innecesarias :).
Ismael Luceno
2009-07-21 at 13:18
Bueno, ahora es mi turno.
* El cut que yo sepa nunca o casi nunca es implementado como una orden interno, así que evitalo como a la peor de las pestes: En lugar de `echo $PIDS | cut -f1` usá ${PIDS%% *}, y sí, es estándar. Mismo para el otro que te queda ${PIDS#* }.
Por lo demás no hay mucho que discutir, salvo del hecho de que siempre esperás a que muera un PID específico y no uno cualquiera. Para lograr eso tendrías que reescribirlo ya que le estás errando al enfoque.
fcr
2009-07-22 at 02:37
¡Ese sí es un buen punto! :D. Sí, fue algo en 5 secs, nomás para llevarle la contra a CMA que quería hacerlo en algún lenguaje raro… el tema es que justamente no se me ocurrió ninguna forma sencilla de esperar por cualquiera… wait te termina esperando por todos…
El tema de los chirimbolos dentro de ${} es que no me acuerdo cuales son estándar y cuales no, por eso no usé XD. Mañana me fijo bien cuales son y cuales no :).
Ismael Luceno
2009-07-22 at 03:37