Evitando ocupado esperando no bash, sem o comando sleep

18

Eu sei que posso esperar que uma condição se torne realidade no bash fazendo:

while true; do
  test_condition && break
  sleep 1
done

Mas cria 1 subprocesso em cada iteração (suspensão). Eu poderia evitá-los fazendo:

while true; do
  test_condition && break
done

Mas usa muito CPU (espera ocupada). Para evitar subprocessos e espera ocupada, encontrei a solução abaixo, mas acho isso feio:

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: no caso geral, não posso simplesmente usar read -t 1 var sem o fifo, porque ele consumirá stdin e não funcionará se stdin não for um terminal ou um pipe.

Posso evitar subprocessos e ocupar a espera de uma forma mais elegante?

    
por jfg956 17.03.2013 / 18:19

8 respostas

15

Nas versões mais recentes de bash (pelo menos v2), os arquivos embutidos podem ser carregados (via enable -f filename commandname ) no tempo de execução. Várias dessas builtins carregáveis também são distribuídas com as fontes bash, e sleep está entre elas. A disponibilidade pode diferir de sistema operacional para sistema operacional (e até de máquina para máquina), é claro. Por exemplo, no openSUSE, estas builtins são distribuídas através do pacote bash-loadables .

Editar: corrija o nome do pacote, adicione a versão mínima do bash.

    
por 17.03.2013 / 22:14
9

A criação de muitos subprocessos é uma coisa ruim em um loop interno. Criar um sleep process por segundo está OK. Não há nada de errado com

while ! test_condition; do
  sleep 1
done

Se você realmente quiser evitar o processo externo, não precisa manter o fifo aberto.

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
    
por 18.03.2013 / 01:47
4

Recentemente tive a necessidade de fazer isso. Eu criei a seguinte função que permitirá que o bash durma para sempre sem chamar nenhum programa externo:

snore()
{
    [[ -n "${_snore_fd:-}" ]] || exec {_snore_fd}<> <(:)
    read ${1:+-t "$1"} -u $_snore_fd || :
}

NOTA: Eu postei anteriormente uma versão disso que abriria e fecharia o descritor de arquivo a cada vez, mas descobri que em alguns sistemas fazendo isso centenas de vezes por segundo acabariam sendo travados. Assim, a nova solução mantém o descritor de arquivo entre as chamadas para a função. O Bash irá limpá-lo na saída de qualquer maneira.

Isso pode ser chamado assim como / bin / sleep, e ele vai dormir pelo tempo solicitado. Chamado sem parâmetros, ele ficará suspenso para sempre.

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

Há um writeup com detalhes excessivos sobre meu blog aqui

    
por 27.11.2017 / 23:47
3

Em ksh93 ou mksh , sleep é um shell embutido, então uma alternativa seria usar esses shells em vez de bash .

zsh também tem zselect builtin (carregado com zmodload zsh/zselect ) que pode dormir por um determinado número de centésimos de segundos com zselect -t <n> .

    
por 17.03.2013 / 19:51
1

Como o usuário yoi disse, se em seu script estiver stdin aberto, então ao invés de dormir 1 você pode simplesmente usar:

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

No Bash versão 4.1 e mais recente, você pode usar o número float, por exemplo read -t 0.3 ...

Se em um script stdin estiver fechado (o script é chamado my_script.sh < /dev/null & ), você precisará usar outro descritor aberto, que não produza saída quando ler for executado, por exemplo. stdout :

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

Se em um script todo o descritor estiver fechado ( stdin , stdout , stderr ) (por exemplo, porque é chamado como daemon), precisa encontrar algum arquivo que não produza saída:

read -t 1 </dev/tty10 3<&- 3<&0 <&3
    
por 18.03.2014 / 16:31
1

Isso funciona a partir de um shell de login, bem como de um shell não interativo.

#!/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
    
por 31.12.2017 / 17:55
0

Você realmente precisa de um fifo? Redirecionar stdin para outro descritor de arquivo também deve funcionar.

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

Inspirado por: Ler entrada no bash dentro de um loop while

    
por 18.03.2013 / 17:15
0

Uma ligeira melhoria nas soluções acima mencionadas (as quais eu baseei isso)

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

# sleep for 10 seconds
bash_sleep 10

Reduziu a necessidade de um fifo e, portanto, nenhuma limpeza para fazer.

    
por 17.09.2018 / 11:28