Pressione qualquer tecla para parar um loop

2

Como fazer o seguinte?

  • Eu tenho um script de loop.
  • Eu quero pará-lo quando eu pressionar uma tecla, caso contrário, ele continuará em 5 segundos.
por TheBlueKingLP 29.05.2017 / 08:25

2 respostas

3

Algo a ter em mente é que shells ou aplicativos que você normalmente executa em um terminal não interagem com um teclado e uma tela.

Eles pegam a entrada de seu stdin como um fluxo de bytes. Quando esse stdin vem de um arquivo regular, os bytes vêm de lá, quando é um pipe, dados que normalmente são enviados por outro processo, quando é algum arquivo de dispositivo que pode vir para dispositivos físicos conectados ao computador. Por exemplo, quando é um dispositivo de caracteres tty, esses dados são enviados por uma linha serial normalmente por um terminal. Um terminal é uma forma de dispositivo que transforma os eventos do teclado em sequências de bytes.

É aí que reside todo o poder das aplicações de terminal. O mecanismo de entrada foi abstraído para eles, para que possam ser usados de forma interativa ou automática em scripts.

Aqui, se você for emitir esse tipo de prompt e esperar por um evento de key , provavelmente desejará que seu aplicativo (seu script) seja apenas interativo. Espera que o stdin seja um terminal ou receba a entrada do terminal, independentemente do que o stdin está aberto.

Agora, como visto acima, todos os aplicativos são fluxos de bytes, que é o terminal (ou emulador de terminal) e o papel da disciplina de linha de dispositivo tty para converter a tecla pressionada em seqüências de bytes. Alguns exemplos:

  • quando você pressiona a tecla a , os terminais ASCII enviam um byte 0x61,
  • quando você pressiona a tecla £ , os terminais UTF-8 enviam os dois bytes 0xc2 e 0xa3.
  • Quando você pressiona a tecla Enter , os terminais ASCII enviam um byte 0x0d cujas disciplinas de linha tty em sistemas baseados em ASCII como o Linux normalmente traduzem para 0x0a
  • Quando você pressiona Ctrl sozinho, os terminais não enviam nada, mas se você pressioná-lo com C , os terminais enviam um byte 0x03 ao qual a disciplina tty intercepta enviar um sinal SIGINT para a tarefa de primeiro plano
  • Quando você pressiona Esquerda , os terminais normalmente enviam uma sequência de bytes (varia de acordo com o terminal, os aplicativos podem consultar o banco de dados terminfo para traduzi-lo), sendo o primeiro 0x1b. Por exemplo, dependendo do modo em que está, xterm , em sistemas baseados em ASCII, enviará 0x1b 0x4f 0x44 ou 0x1b 0x5b 0x44 ( <ESC>[A ou <ESC>OA ).

Então, as perguntas que eu colocaria seriam:

  1. Você ainda deseja avisar ao usuário se o stdin não é um terminal
  2. Se a resposta para 1 for sim, você deseja avisar o usuário no terminal ou por stdin / stdout?
  3. Se a resposta para 1 for não, você ainda deseja esperar 5 segundos entre cada iteração?
  4. Se a resposta a 2 for através do terminal , o script deve ser abortado se não puder detectar um terminal de controle ou voltar a um modo não-terminal?
  5. Você deseja considerar apenas as teclas pressionadas após ter emitido o aviso. IOW, se o usuário acidentalmente digitar uma chave antes de o prompt ser emitido.
  6. A quanto tempo você deseja ir para garantir que apenas leia os bytes emitidos para uma única tecla pressionada?

Aqui, suponho que você queira que seu script seja apenas um aplicativo interativo do terminal e interaja apenas pelo terminal de controle, deixando o stdin / stdout sozinho.

#! /bin/sh -

# ":" being a special builtin, POSIX requires it to exit if a
# redirection fails, which makes this a way to easily check if a
# controlling terminal is present and readable:
:</dev/tty

# if using bash however not in POSIX conformance mode, you'll need to
# change it to something like:
exec 3< /dev/tty 3<&- || exit

read_key_with_timeout() (
  timeout=$1 prompt=$2
  saved_tty_settings=$(stty -g) || exit

  # if we're killed, restore the tty settings, the convoluted part about
  # killing the subshell process is to work around a problem in shells
  # like bash that ignore a SIGINT if the current command being run handles
  # it.
  for sig in INT TERM QUIT; do
    trap '
      stty "$saved_tty_settings"
      trap - '"$sig"'
      pid=$(exec sh -c '\''echo "$PPID"'\'')
      kill -s '"$sig"' "$pid"

      # fall back if kill failed above
      exit 2' "$sig"
  done

  # drain the tty's buffer
  stty -icanon min 0 time 0; cat > /dev/null

  printf '%s\n' "$prompt"

  # use the tty line discipline features to say the next read()
  # should wait at most the given number of deciseconds (limited to 255)
  stty time "$((timeout * 10))" -echo

  # do one read and count the bytes returned
  count=$(dd 2> /dev/null count=1 | wc -c)

  # If the user pressed a key like the £ or Home ones described above
  # it's likely all the corresponding bytes will have been read by dd
  # above, but not guaranteed, so we may want to drain the tty buffer
  # again to make sure we don't leave part of the sequence sent by a
  # key press to be read by the next thing that reads from the tty device
  # thereafter. Here allowing the terminal to send bytes as slow as 10
  # per second. Doing so however, we may end up reading the bytes sent
  # upon subsequent key presses though.
  stty time 1; cat > /dev/null

  stty "$saved_tty_settings"

  # return whether at least one byte was read:
  [ "$(($count))" -gt 0 ]

) <> /dev/tty >&0 2>&0

until
  echo "Hello World"
  sleep 1
  echo "Done greeting the world"
  read_key_with_timeout 5 "Press any key to stop"
do
  continue
done
    
por 29.05.2017 / 12:33
0
while true; do
    echo 'Looping, press Ctrl+C to exit'
    sleep 5
done

Não há necessidade de torná-lo mais complicado do que isso.

O seguinte requer bash :

while true; do
    echo 'Press any key to exit, or wait 5 seconds'
    if read -r -N 1 -t 5; then
        break
    fi
done

Se o read falhar (expirando), o loop continua.

    
por 29.05.2017 / 08:27