Evitare l'attesa occupata in bash, senza il comando sleep


19

So di poter aspettare che una condizione si avveri in bash facendo:

while true; do
  test_condition && break
  sleep 1
done

Ma crea 1 sottoprocesso ad ogni iterazione (sleep). Potrei evitarli facendo:

while true; do
  test_condition && break
done

Ma utilizza molta CPU (attesa in attesa). Per evitare sottoprocessi e attese impegnative, ho trovato la soluzione qui sotto, ma la trovo brutta:

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: nel caso generale, non posso semplicemente usare read -t 1 varsenza il fifo, perché consumerà stdin e non funzionerà se stdin non è un terminale o una pipe.

Posso evitare sottoprocessi e attese impegnate in un modo più elegante?


1
trueè incorporato e non crea un processo secondario in bash. l'attesa impegnata sarà sempre negativa.
Giordania,

@joranm: hai ragione true, domanda aggiornata.
jfg956,

Perché non senza FIFO? Semplicemente read -t 1 var.
ott--

@ott: hai ragione, ma questo consumerà stdin. Inoltre, non funzionerà se stdin non è un terminale o una pipe.
jfg956,

Se la manutenibilità è un problema, suggerirei caldamente di utilizzare il sleepcome nel primo esempio. Il secondo, sebbene possa funzionare, non sarà facile per nessuno adattarsi in futuro. Il codice semplice ha anche un maggiore potenziale per essere sicuro.
Kusalananda

Risposte:


17

Nelle versioni più recenti di bash(almeno v2), i runtime incorporati possono essere caricati (tramite enable -f filename commandname) in fase di esecuzione. Un certo numero di tali builtin caricabili è anche distribuito con le fonti bash ed sleepè tra questi. La disponibilità può variare da sistema operativo a sistema operativo (e persino da macchina a macchina), ovviamente. Ad esempio, su openSUSE, questi builtin sono distribuiti tramite il pacchetto bash-loadables.

Modifica: correggi il nome del pacchetto, aggiungi la versione minima di bash.


Wow, questo è quello che sto cercando, e sicuramente imparerò qualcosa sull'integrato caricabile: +1. Ci proverò, eppure è la risposta migliore.
jfg956,

1
Funziona ! Su debian, il pacchetto è bash-builtins. Include solo fonti e il Makefile deve essere modificato, ma sono stato in grado di installarlo sleepcome incorporato. Grazie.
jfg956,

9

La creazione di molti sottoprocessi è una cosa negativa in un ciclo interno. La creazione di un sleepprocesso al secondo è OK. Non c'è niente di sbagliato in

while ! test_condition; do
  sleep 1
done

Se vuoi davvero evitare il processo esterno, non devi tenere aperto il 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

Hai ragione sul fatto che un processo al secondo sia arachidi (ma la mia domanda era su come trovare un modo per rimuoverlo). Per quanto riguarda la versione più corta, è più bella della mia, quindi +1 (ma ho rimosso il modo mkdirin cui è fatto mktemp(in caso contrario, è una condizione di gara)). Vero anche su ciò while ! test_condition;che è più bello della mia soluzione iniziale.
jfg956,

7

Di recente ho avuto bisogno di farlo. Mi è venuta in mente la seguente funzione che permetterà a bash di dormire per sempre senza chiamare alcun programma esterno:

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: in precedenza avevo pubblicato una versione di questo che apriva e chiudeva ogni volta il descrittore di file, ma ho scoperto che su alcuni sistemi che lo facessero centinaia di volte al secondo alla fine si sarebbe bloccato. Pertanto, la nuova soluzione mantiene il descrittore di file tra le chiamate alla funzione. Bash lo pulirà comunque all'uscita.

Questo può essere chiamato proprio come / bin / sleep e dormirà per il tempo richiesto. Chiamato senza parametri, si bloccherà per sempre.

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

C'è un writeup con dettagli eccessivi sul mio blog qui


1
Eccellente blog. Tuttavia, sono andato lì in cerca di una spiegazione del perché read -t 10 < <(:)ritorni immediatamente mentre read -t 10 <> <(:)attende tutti i 10 secondi, ma ancora non capisco.
Amir

In read -t 10 <> <(:)cosa significa <>?
CodeMedic

<> apre il descrittore di file per la lettura e la scrittura, anche se la sostituzione del processo sottostante <(:) consente solo la lettura. Questo è un hack che induce Linux, e in particolare Linux, a supporre che qualcuno potrebbe scrivergli, quindi la lettura si bloccherà in attesa di input che non arriveranno mai. Non lo farà sui sistemi BSD, nel qual caso la soluzione alternativa prenderà il via.
Bolt

3

In ksh93o mksh, sleepè incorporata una shell, quindi un'alternativa potrebbe essere quella di utilizzare quelle shell invece di bash.

zshha anche un zselectbuilt-in (caricato con zmodload zsh/zselect) che può dormire per un dato numero di centesimi di secondo con zselect -t <n>.


2

Come hai detto l' utente , se nel tuo script è aperto stdin , allora invece di dormire 1 puoi semplicemente usare:

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

In Bash versione 4.1 e successive è possibile utilizzare il numero float, ad es read -t 0.3 ...

Se in uno script lo stdin è chiuso (lo script viene chiamato my_script.sh < /dev/null &), è necessario utilizzare un altro descrittore aperto, che non produce output quando viene eseguita la lettura , ad es. stdout :

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

Se in uno script tutti i descrittori sono chiusi ( stdin , stdout , stderr ) (ad es. Perché viene chiamato come demone), allora è necessario trovare qualsiasi file esistente che non produca output:

read -t 1 </dev/tty10 3<&- 3<&0 <&3

read -t 1 3<&- 3<&0 <&3è lo stesso di read -t 0. Sta solo leggendo da stdin con timeout.
Stéphane Chazelas,

1

Funziona da una shell di login e da una shell non interattiva.

#!/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

Questo ha funzionato anche su Mac OS X v10.12.6
b01

1
Questo non è raccomandato Se più script lo usano contemporaneamente, ottengono tutti SIGSTOP mentre cercano di leggere lo stdin. Il tuo stdin viene bloccato mentre questo aspetta. Non usare stdin per questo. Volete nuovi descrittori di file diversi.
Normadize,

1
@Normadize Qui c'è un'altra risposta ( unix.stackexchange.com/a/407383/147685 ) che si occupa della preoccupazione dell'uso di descrittori di file gratuiti. La sua versione minima è read -t 10 <> <(:).
Amir


0

Un leggero miglioramento rispetto alle soluzioni sopra menzionate (su cui ho basato questo).

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

# sleep for 10 seconds
bash_sleep 10

Ridotta la necessità di un fifo e quindi nessuna pulizia da fare.


1
Questo non è raccomandato Se più script lo usano contemporaneamente, ottengono tutti SIGSTOP mentre cercano di leggere lo stdin. Il tuo stdin viene bloccato mentre questo aspetta. Non usare stdin per questo. Volete nuovi descrittori di file diversi.
Normadize,

@Normadize Non ci ho mai pensato; per favore, puoi elaborarmi o indicarmi una risorsa in cui posso leggere di più al riguardo.
CodeMedic,

@CodeMedic C'è un'altra risposta qui ( unix.stackexchange.com/a/407383/147685 ) che si occupa della preoccupazione dell'uso di descrittori di file gratuiti. La sua versione minima è read -t 10 <> <(:).
Amir
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.