Evitando a espera ocupada no bash, sem o comando sleep

19

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 ele cria 1 subprocesso a cada iteração (suspensão). Eu poderia evitá-los fazendo:

while true; do
  test_condition && break
done

Mas ele usa muita CPU (espera ocupada). Para evitar subprocessos e espera ocupada, vim com a solução abaixo, mas acho feia:

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 varsem o fifo, porque ele consumirá o stdin e não funcionará se o stdin não for um terminal ou um pipe.

Posso evitar subprocessos e espera ocupada de uma maneira mais elegante?

jfg956
fonte
1
trueé um builtin e não cria um subprocesso no bash. A espera ocupada sempre será ruim.
Jordanm 17/03
@joranm: você está certo true, pergunta atualizada.
jfg956
Por que não sem o fifo? Simplesmente read -t 1 var.
17--13
@ott: você está certo, mas isso consumirá stdin. Além disso, não funcionará se o stdin não for um terminal ou um tubo.
jfg956
Se a manutenção for um problema, eu sugeriria fortemente que seguisse sleepo exemplo do primeiro exemplo. O segundo, embora funcione, não será fácil para ninguém se adaptar no futuro. O código simples também tem um potencial maior de segurança.
Kusalananda

Respostas:

17

Nas versões mais recentes bash(pelo menos v2), os componentes internos podem ser carregados (via enable -f filename commandname) no tempo de execução. Vários desses embutidos carregáveis ​​também são distribuídos com as fontes do bash e sleepestão entre eles. A disponibilidade pode variar de sistema operacional para sistema operacional (e até de máquina para máquina), é claro. Por exemplo, no openSUSE, esses built-in são distribuídos através do pacote bash-loadables.

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

Ansgar Esztermann
fonte
Uau, é isso que estou procurando e definitivamente aprendo algo sobre o built-in carregável: +1. Vou tentar isso, e ainda é a melhor resposta.
jfg956
1
Funciona ! No debian, o pacote é bash-builtins. Ele inclui apenas fontes e o Makefile deve ser editado, mas eu consegui instalar sleepcomo um builtin. Obrigado.
jfg956
9

Criar muitos subprocessos é uma coisa ruim em um loop interno. Criar um sleepprocesso por segundo é bom. Não há nada errado com

while ! test_condition; do
  sleep 1
done

Se você realmente deseja 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
Gilles 'SO- parar de ser mau'
fonte
Você está certo sobre um processo por segundo ser amendoim (mas minha pergunta era sobre encontrar uma maneira de removê-lo). Sobre a versão mais curta, é mais agradável que a minha, então +1 (mas removi o mkdirque é feito por mktemp(se não, é uma condição de corrida)). Também é verdade sobre o while ! test_condition;que é melhor do que a minha solução inicial.
jfg956
7

Recentemente, tive uma necessidade de fazer isso. Eu vim com a seguinte função que permitirá que o bash durma para sempre sem chamar nenhum 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: Eu publiquei anteriormente uma versão disso que abriria e fecharia o descritor de arquivos a cada vez, mas descobri que em alguns sistemas fazendo isso centenas de vezes por segundo acabaria travando. Portanto, a nova solução mantém o descritor de arquivo entre as chamadas para a função. Bash irá limpá-lo na saída de qualquer maneira.

Isso pode ser chamado como / bin / sleep e permanecerá no tempo solicitado. Chamado sem parâmetros, ele travará para sempre.

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

Há um artigo com detalhes excessivos no meu blog aqui

parafuso
fonte
1
Excelente entrada no blog. No entanto, eu fui lá procurando uma explicação por que read -t 10 < <(:)retorna imediatamente enquanto read -t 10 <> <(:)aguarda os 10 segundos completos, mas ainda não entendi.
Amir
Em read -t 10 <> <(:)que <>representa?
CodeMedic
<> abre o descritor de arquivo para leitura e gravação, mesmo que a substituição do processo subjacente <(:) permita apenas a leitura. Este é um hack que faz com que o Linux, e especificamente o Linux, suponha que alguém possa gravá-lo; portanto, a leitura ficará parada aguardando a entrada que nunca chegará. Ele não fará isso nos sistemas BSD; nesse caso, a solução alternativa será iniciada.
bolt
3

Em ksh93ou mksh, sleepé um shell embutido, portanto, uma alternativa pode ser usar essas conchas em vez de bash.

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

Scrutinizer
fonte
2

Como o usuário yoi disse, se no seu script for aberto o stdin , então, em vez do sleep 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 da bóia, por exemplo read -t 0.3 ...

Se em um script o stdin estiver fechado (o script é chamado my_script.sh < /dev/null &), você precisará usar outro descritor aberto, que não produza saída quando a leitura é executada, 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), será necessário encontrar qualquer arquivo existente que não produza saída:

read -t 1 </dev/tty10 3<&- 3<&0 <&3
Mysak
fonte
read -t 1 3<&- 3<&0 <&3é o mesmo que read -t 0. É apenas a leitura de stdin com timeout.
Stéphane Chazelas
1

Isso funciona a partir de um shell de login e 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
Omar
fonte
Isso também funcionou no Mac OS X v10.12.6
b01
1
Isto não é recomendado. Se vários scripts usarem isso ao mesmo tempo, todos serão SIGSTOP'ed enquanto todos tentam ler stdin. Seu stdin fica bloqueado enquanto isso aguarda. Não use stdin para isso. Você deseja novos descritores de arquivos diferentes.
Normadize
1
@ Normadize Há outra resposta aqui ( unix.stackexchange.com/a/407383/147685 ) que trata da preocupação de usar descritores de arquivo gratuitos. Sua versão mínima é read -t 10 <> <(:).
Amir
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: Leia a entrada no bash dentro de um loop while

yoi
fonte
1
Isso não está dormindo, ainda está consumindo stdin do terminal.
jfg956
0

Uma ligeira melhoria nas soluções mencionadas acima (nas quais 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 a ser feita.

CodeMedic
fonte
1
Isto não é recomendado. Se vários scripts usarem isso ao mesmo tempo, todos serão SIGSTOP'ed enquanto todos tentam ler stdin. Seu stdin fica bloqueado enquanto isso aguarda. Não use stdin para isso. Você deseja novos descritores de arquivos diferentes.
precisa saber é o seguinte
@ Normadize Nunca pensei nisso; por favor, você pode elaborar ou apontar um recurso para ler mais sobre ele.
CodeMedic 22/09
@CodeMedic Há outra resposta aqui ( unix.stackexchange.com/a/407383/147685 ) que trata da preocupação de usar descritores de arquivo gratuitos. Sua versão mínima é read -t 10 <> <(:).
Amir