Evitar ocupados esperando en bash, sin el comando sleep

19

Sé que puedo esperar que una condición se haga realidad en bash haciendo:

while true; do
  test_condition && break
  sleep 1
done

Pero crea 1 subproceso en cada iteración (suspensión). Podría evitarlos haciendo:

while true; do
  test_condition && break
done

Pero usa mucha CPU (espera ocupada). Para evitar subprocesos y esperas ocupadas, se me ocurrió la solución a continuación, pero me parece fea:

my_tmp_dir=$(mktemp -d --tmpdir=/tmp)    # Create a unique tmp dir for the fifo.
mkfifo $my_tmp_dir/fifo                  # Create an empty fifo for sleep by read.
exec 3<> $my_tmp_dir/fifo                # Open the fifo for reading and writing.

while true; do
  test_condition && break
  read -t 1 -u 3 var                     # Same as sleep 1, but without sub-process.
done

exec 3<&-                                # Closing the fifo.
rm $my_tmp_dir/fifo; rmdir $my_tmp_dir   # Cleanup, could be done in a trap.

Nota: en el caso general, no puedo simplemente usarlo read -t 1 varsin el fifo, porque consumirá stdin y no funcionará si stdin no es un terminal o una tubería.

¿Puedo evitar los subprocesos y la espera ocupada de una manera más elegante?

jfg956
fuente
1
truees incorporado y no crea un subproceso en bash. La espera ocupada siempre será mala.
jordanm
@joranm: tienes razón true, pregunta actualizada.
jfg956
¿Por qué no sin fifo? En pocas read -t 1 var.
ott--
@ott: tienes razón, pero esto consumirá stdin. Además, no funcionará si stdin no es un terminal o una tubería.
jfg956
Si la mantenibilidad es un problema, sugeriría encarecidamente ir con el sleepcomo en el primer ejemplo. El segundo, si bien puede funcionar, no va a ser fácil para nadie en el futuro. El código simple también tiene un mayor potencial para ser seguro.
Kusalananda

Respuestas:

17

En las versiones más recientes de bash(al menos v2), los builtins se pueden cargar (vía enable -f filename commandname) en tiempo de ejecución. Un número de tales construcciones cargables también se distribuye con las fuentes bash, y se sleepencuentra entre ellas. La disponibilidad puede variar de un sistema operativo a otro (e incluso de máquina a máquina), por supuesto. Por ejemplo, en openSUSE, estos componentes internos se distribuyen a través del paquete bash-loadables.

Editar: arregle el nombre del paquete, agregue la versión mínima de bash.

Ansgar Esztermann
fuente
Wow, esto es lo que estoy buscando, y definitivamente aprendo algo acerca de lo que se puede cargar: +1. Intentaré esto y, sin embargo, es la mejor respuesta.
jfg956
1
Funciona ! En debian, el paquete es bash-builtins. Solo incluye fuentes y el Makefile debe editarse, pero pude instalarlo sleepcomo un archivo incorporado. Gracias.
jfg956
9

Crear muchos subprocesos es algo malo en un bucle interno. Crear un sleepproceso por segundo está bien. No hay nada malo con

while ! test_condition; do
  sleep 1
done

Si realmente desea evitar el proceso externo, no necesita mantener abierto el Fifo.

my_tmpdir=$(mktemp -d)
trap 'rm -rf "$my_tmpdir"' 0
mkfifo "$my_tmpdir/f"

while ! test_condition; do
  read -t 1 <>"$my_tmpdir/f"
done
Gilles 'SO- deja de ser malvado'
fuente
Tienes razón acerca de que un proceso por segundo es maní (pero mi pregunta fue sobre encontrar una manera de eliminarlo). Acerca de la versión más corta, es mejor que la mía, por lo que +1 (pero eliminé el mkdircomo está hecho mktemp(si no, es una condición de carrera)). También es cierto lo while ! test_condition;que es mejor que mi solución inicial.
jfg956
7

Recientemente tuve la necesidad de hacer esto. Se me ocurrió la siguiente función que permitirá que bash duerma para siempre sin llamar a ningún programa externo:

snore()
{
    local IFS
    [[ -n "${_snore_fd:-}" ]] || { exec {_snore_fd}<> <(:); } 2>/dev/null ||
    {
        # workaround for MacOS and similar systems
        local fifo
        fifo=$(mktemp -u)
        mkfifo -m 700 "$fifo"
        exec {_snore_fd}<>"$fifo"
        rm "$fifo"
    }
    read ${1:+-t "$1"} -u $_snore_fd || :
}

NOTA: Publiqué previamente una versión de esto que abriría y cerraría el descriptor de archivo cada vez, pero descubrí que en algunos sistemas que lo hacen cientos de veces por segundo eventualmente se bloqueará. Por lo tanto, la nueva solución mantiene el descriptor de archivo entre llamadas a la función. Bash lo limpiará a la salida de todos modos.

Esto se puede llamar como / bin / sleep, y se dormirá durante el tiempo solicitado. Llamado sin parámetros, se colgará para siempre.

snore 0.1  # sleeps for 0.1 seconds
snore 10   # sleeps for 10 seconds
snore      # sleeps forever

Hay una reseña con detalles excesivos en mi blog aquí.

tornillo
fuente
1
Excelente entrada de blog. Sin embargo, fui allí buscando una explicación de por qué read -t 10 < <(:)regresa de inmediato mientras read -t 10 <> <(:)espera los 10 segundos completos, pero aún no lo entiendo.
Amir
read -t 10 <> <(:)¿ En qué significa <>?
CodeMedic
<> abre el descriptor de archivo para leer y escribir, aunque la sustitución del proceso subyacente <(:) solo permite la lectura. Este es un truco que hace que Linux, y Linux específicamente, asuma que alguien podría escribirle, por lo que leer se colgará esperando una entrada que nunca llegará. No hará esto en los sistemas BSD, en cuyo caso la solución comenzará.
atornille el
3

En ksh93o mksh, sleephay una concha incorporada, por lo que una alternativa podría ser usar esas conchas en lugar de bash.

zshtambién tiene un zselectincorporado (cargado con zmodload zsh/zselect) que puede dormir durante un número determinado de centésimas de segundo con zselect -t <n>.

Escrutador
fuente
2

Como dijo el usuario yoi , si en su script se abre stdin , entonces en lugar de dormir 1 simplemente puede usar:

read -t 1 3<&- 3<&0 <&3

En Bash versión 4.1 y posteriores, puede usar el número flotante, p. Ej. read -t 0.3 ...

Si en un script, stdin está cerrado (se llama script my_script.sh < /dev/null &), entonces necesita usar otro descriptor abierto, que no produce salida cuando se ejecuta la lectura , por ejemplo. stdout :

read -t 1 <&1 3<&- 3<&0 <&3

Si en un script todo el descriptor está cerrado ( stdin , stdout , stderr ) (por ejemplo, porque se llama como daemon), entonces necesita encontrar cualquier archivo existente que no produzca resultados:

read -t 1 </dev/tty10 3<&- 3<&0 <&3
Mysak
fuente
read -t 1 3<&- 3<&0 <&3es el mismo que read -t 0. Es solo leer de stdin con tiempo de espera.
Stéphane Chazelas
1

Esto funciona desde un shell de inicio de sesión, así como desde un shell no interactivo.

#!/bin/sh

# to avoid starting /bin/sleep each time we call sleep, 
# make our own using the read built in function
xsleep()
{
  read -t $1 -u 1
}

# usage
xsleep 3
Omar
fuente
Esto también funcionó en Mac OS X v10.12.6
b01
1
Esto no es recomendable. Si varios scripts usan esto al mismo tiempo, todos se SIGSTOP 'mientras intentan leer stdin. Tu stdin se bloquea mientras esto espera. No uses stdin para esto. Desea nuevos descriptores de archivo diferentes.
Normadize
1
@Normadize Hay otra respuesta aquí ( unix.stackexchange.com/a/407383/147685 ) que trata la preocupación de usar descriptores de archivos gratuitos. Su versión mínima básica es read -t 10 <> <(:).
Amir
0

¿Realmente necesitas un quince? Redirigir stdin a otro descriptor de archivo también debería funcionar.

{
echo line | while read line; do
   read -t 1 <&3
   echo "$line"
done
} 3<&- 3<&0

Inspirado por: Leer la entrada en bash dentro de un bucle while

yoi
fuente
1
Esto no está durmiendo, todavía está consumiendo stdin desde la terminal.
jfg956
0

Una ligera mejora en las soluciones mencionadas anteriormente (en las que he basado esto).

bash_sleep() {
    read -rt "${1?Specify sleep interval in seconds}" -u 1 <<<"" || :;
}

# sleep for 10 seconds
bash_sleep 10

Se redujo la necesidad de un fifo y, por lo tanto, no hay que limpiar.

CodeMedic
fuente
1
Esto no es recomendable. Si varios scripts usan esto al mismo tiempo, todos se SIGSTOP 'mientras intentan leer stdin. Tu stdin se bloquea mientras esto espera. No uses stdin para esto. Desea nuevos descriptores de archivo diferentes.
Normadize
@Normadize Nunca pensé en eso; por favor, ¿puede explicarme o señalarme un recurso donde pueda leer más sobre él?
CodeMedic
@CodeMedic Hay otra respuesta aquí ( unix.stackexchange.com/a/407383/147685 ) que trata la preocupación de usar descriptores de archivos gratuitos. Su versión mínima básica es read -t 10 <> <(:).
Amir