¿Por qué no puedo usar el control de trabajo en un script bash?

En esta respuesta a otra pregunta , me dijeron que

en las secuencias de comandos no tienes control del trabajo (y tratar de activarlo es estúpido)

Esta es la primera vez que escucho esto, y he estudiado minuciosamente la sección bash.info en Job Control (capítulo 7), sin encontrar ninguna mención a ninguna de estas afirmaciones. [ Actualización: la página man es un poco mejor, menciona el uso “típico”, la configuración predeterminada y la E / S del terminal, pero no hay una razón real por la que el control del trabajo sea especialmente desaconsejable para los scripts.]

Entonces, ¿por qué no funciona el control de trabajos basado en secuencias de comandos y qué lo convierte en una mala práctica (también conocido como “estúpido”)?

Editar: El script en cuestión inicia un proceso en segundo plano, inicia un segundo proceso en segundo plano e intenta volver a poner el primer proceso en primer plano para que tenga E / S de terminal normal (como si se ejecutara directamente), que luego se puede redireccionar fuera del guion No se puede hacer eso en un proceso en segundo plano.

Como se señala en la respuesta aceptada a la otra pregunta, existen otros scripts que resuelven ese problema en particular sin intentar controlar el trabajo. Multa. Y el script lambasted utiliza un número de trabajo codificado, obviamente malo. Pero estoy tratando de entender si el control del trabajo es un enfoque fundamentalmente condenado. Todavía parece que tal vez podría funcionar …

Lo que quiso decir es que el control del trabajo está desactivado por defecto en modo no interactivo (es decir, en un script).

Desde la página del hombre bash :

 JOB CONTROL Job control refers to the ability to selectively stop (suspend) the execution of processes and continue (resume) their execution at a later point. A user typically employs this facility via an interactive interface supplied jointly by the system's terminal driver and bash. 

y

  set [--abefhkmnptuvxBCHP] [-o option] [arg ...] ... -m Monitor mode. Job control is enabled. This option is on by default for interactive shells on systems that support it (see JOB CONTROL above). Background processes run in a separate process group and a line containing their exit status is printed upon their completion. 

Cuando dijo “es estúpido” quiso decir que no solo:

  1. El control del trabajo está destinado principalmente a facilitar el control interactivo (mientras que un script puede trabajar directamente con el pid), pero también
  2. Cito su respuesta original, … se basa en el hecho de que no comenzaste ningún otro trabajo previamente en el guión, lo cual es una mala suposición . Lo cual es bastante correcto.

ACTUALIZAR

En respuesta a su comentario: sí, nadie le impedirá utilizar el control del trabajo en su script bash; no hay ninguna razón para desactivar forzosamente el set -m (es decir, sí, el control del trabajo del script funcionará si así lo desea). ) Recuerda que al final, especialmente en las secuencias de comandos, siempre hay más de una manera de despellejar a un gato, pero algunas formas son más portátiles, más confiables, hacen que sea más sencillo manejar casos de error, analizar el resultado, etc.

lhunath circunstancias particulares pueden o no justificar una forma diferente de lo que lhunath (y otros usuarios) consideran “mejores prácticas”.

El control de trabajos con bg y fg es útil solo en shells interactivos. Pero & junto con wait es útil en scripts también.

En sistemas multiprocesador, generar trabajos en segundo plano puede mejorar en gran medida el rendimiento del script, por ejemplo, en scripts de comstackción en los que desee iniciar al menos un comstackdor por CPU o procesar imágenes con las herramientas de ImageMagick de forma paralela, etc.

El siguiente ejemplo ejecuta hasta 8 gcc paralelas para comstackr todos los archivos fuente en una matriz:

 #!bash ... for ((i = 0, end=${#sourcefiles[@]}; i < end;)); do for ((cpu_num = 0; cpu_num < 8; cpu_num++, i++)); do if ((i < end)); then gcc ${sourcefiles[$i]} & fi done wait done 

No hay nada "estúpido" sobre esto. Pero requerirá el comando de wait , que espera todos los trabajos en segundo plano antes de que el script continúe. El PID del último trabajo de fondo se almacena en $! variable, por lo que también puede wait ${!} . Tenga en cuenta también el nice comando.

A veces, dicho código es útil en los archivos make:

 buildall: for cpp_file in *.cpp; do gcc -c $$cpp_file & done; wait 

Esto da un control mucho más fino que make -j .

Tenga en cuenta que & es un terminador de línea como ; (escribir command& no command&; ).

Espero que esto ayude.

El control de trabajos es útil solo cuando se ejecuta un shell interactivo, es decir, se sabe que stdin y stdout están conectados a un dispositivo terminal (/ dev / pts / * en Linux). Entonces, tiene sentido tener algo en primer plano, otra cosa en el fondo, etc.

Scripts, por otro lado, no tiene esa garantía. Los scripts pueden hacerse ejecutables y ejecutarse sin ningún terminal conectado. No tiene sentido tener procesos en primer plano o en segundo plano en este caso.

Sin embargo, puede ejecutar otros comandos de forma no interactiva en el fondo (agregando “&” a la línea de comando) y capturar sus PID con $! . Luego utilizas kill para matarlos o suspenderlos (simulando Ctrl-C o Ctrl-Z en el terminal, el shell era interactivo). También puede usar wait (en lugar de fg ) para esperar a que finalice el proceso en segundo plano.

Podría ser útil activar el control del trabajo en un script para establecer capturas en SIGCHLD. La sección JOB CONTROL en el manual dice:

El shell aprende inmediatamente cada vez que un trabajo cambia de estado. Normalmente, bash espera hasta que esté a punto de imprimir un mensaje antes de informar los cambios en el estado de un trabajo para no interrumpir ningún otro resultado. Si la opción -b del comando integrado del conjunto está habilitada, bash informa dichos cambios de inmediato. Cualquier trampa en SIGCHLD se ejecuta para cada niño que sale.

(el énfasis es mío)

Tome la siguiente secuencia de comandos, como un ejemplo:

 dualbus@debian:~$ cat children.bash #!/bin/bash set -m count=0 limit=3 trap 'counter && { job & }' CHLD job() { local amount=$((RANDOM % 8)) echo "sleeping $amount seconds" sleep "$amount" } counter() { ((count++ < limit)) } counter && { job & } wait dualbus@debian:~$ chmod +x children.bash dualbus@debian:~$ ./children.bash sleeping 6 seconds sleeping 0 seconds sleeping 7 seconds 

Nota: la captura de CHLD parece estar rota a partir de la versión 4.3

En la versión 4.3, podría usar 'wait -n' para lograr lo mismo, sin embargo:

 dualbus@debian:~$ cat waitn.bash #!/home/dualbus/local/bin/bash count=0 limit=3 trap 'kill "$pid"; exit' INT job() { local amount=$((RANDOM % 8)) echo "sleeping $amount seconds" sleep "$amount" } for ((i=0; i0)) && wait -n; job & pid=$! done dualbus@debian:~$ chmod +x waitn.bash dualbus@debian:~$ ./waitn.bash sleeping 3 seconds sleeping 0 seconds sleeping 5 seconds 

Podría argumentar que hay otras formas de hacerlo de una manera más portátil, es decir, sin CHLD o espera -n:

 dualbus@debian:~$ cat portable.sh #!/bin/sh count=0 limit=3 trap 'counter && { brand; job & }; wait' USR1 unset RANDOM; rseed=123459876$$ brand() { [ "$rseed" -eq 0 ] && rseed=123459876 h=$((rseed / 127773)) l=$((rseed % 127773)) rseed=$((16807 * l - 2836 * h)) RANDOM=$((rseed & 32767)) } job() { amount=$((RANDOM % 8)) echo "sleeping $amount seconds" sleep "$amount" kill -USR1 "$$" } counter() { [ "$count" -lt "$limit" ]; ret=$? count=$((count+1)) return "$ret" } counter && { brand; job & } wait dualbus@debian:~$ chmod +x portable.sh dualbus@debian:~$ ./portable.sh sleeping 2 seconds sleeping 5 seconds sleeping 6 seconds 

Entonces, en conclusión, set -m no es tan útil en los scripts, ya que la única característica interesante que aporta a los scripts es poder trabajar con SIGCHLD. Y hay otras formas de lograr lo mismo, ya sea más corto (espera -n) o más portátil (enviando señales usted mismo).

Bash sí admite el control del trabajo, como dices. En la escritura de scripts de shell, a menudo se supone que no puede confiar en el hecho de que tiene bash, sino que tiene el shell Bourne shell ( sh ), que históricamente no tenía control de trabajo.

Me siento en apuros estos días para imaginarme un sistema en el que honestamente estés restringido al verdadero caparazón de Bourne. La mayoría de los sistemas ‘ /bin/sh estarán vinculados a bash . Aún así, es posible. Una cosa que puedes hacer es en lugar de especificar

 #!/bin/sh 

Tu puedes hacer:

 #!/bin/bash 

Eso, y su documentación, dejarían en claro que su script necesita bash .

Posiblemente o / t, pero a menudo uso nohup cuando ssh en un servidor en un trabajo de larga duración, de modo que si me desconecto, el trabajo aún se completa.

Me pregunto si las personas se confunden al detenerse y comenzar desde un shell maestro interactivo y procesos de fondo de desove. El comando de espera le permite generar un montón de cosas y luego esperar a que se completen todas, y como dije, uso nohup todo el tiempo. Es más complejo que esto y muy poco utilizado; sh también admite este modo. Eche un vistazo al manual.

También tienes

 kill -STOP pid 

A menudo hago eso si quiero suspender el sudo actual, como en:

 kill -STOP $$ 

Pero ay de ti si saltas al cascarón de un editor, todo se quedará allí.

Tiendo a usar mnemotécnico-KILL, etc. porque existe el peligro de tipear

 kill - 9 pid # note the space 

y en los viejos tiempos a veces podías bajar la máquina porque mataría a init.

trabajos SI funcionan en scripts bash

PERO, usted … NECESITA vigilar al personal engendrado como:

 ls -1 /usr/share/doc/ | while read -r doc ; do ... done 

trabajos tendrán un contexto diferente en cada lado del |

eludir esto puede estarse utilizando para en lugar de while:

 for `ls -1 /usr/share/doc` ; do ... done 

esto debería demostrar cómo usar trabajos en un script … con la mención de que mi nota comentada es … REAL (no sé por qué ese comportamiento)

  #!/bin/bash for i in `seq 7` ; do ( sleep 100 ) & done jobs while [ `jobs | wc -l` -ne 0 ] ; do for jobnr in `jobs | awk '{print $1}' | cut -d\[ -f2- |cut -d\] -f1` ; do kill %$jobnr done #this is REALLY ODD ... but while won't exit without this ... dunno why jobs >/dev/null 2>/dev/null done sleep 1 jobs