Pressione qualquer tecla para interromper um loop

Pressione qualquer tecla para interromper um loop

Como fazer o seguinte?

  • Eu tenho um script de loop.
  • Quero pará-lo quando pressiono uma tecla, caso contrário, continuará em 5 segundos.

Responder1

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

Eles recebem a entrada de seu stdin como um fluxo de bytes. Quando aquele stdin vem de um arquivo normal, os bytes vêm daí, quando é um pipe, são dados normalmente enviados por outro processo, quando é algum arquivo de dispositivo que pode chegar a dispositivos físicos conectados ao computador. Por exemplo, quando é um dispositivo de caractere tty, são dados enviados por alguma linha serial, normalmente por um terminal. Um terminal é alguma forma de dispositivo que transforma eventos de teclado em sequências de bytes.

É aí que reside todo o poder dos aplicativos 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 aviso e esperar umchavepress, você provavelmente deseja que seu aplicativo (seu script) seja apenas interativo. Espere que o stdin seja um terminal ou receba a entrada do terminal, independentemente do stdin estar aberto.

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

  • quando você pressiona a atecla, os terminais ASCII enviam um byte 0x61,
  • ao pressionar a £tecla, os terminais UTF-8 enviam os dois bytes 0xc2 e 0xa3.
  • Quando você pressiona a Entertecla, os terminais ASCII enviam um byte 0x0d que as disciplinas de linha tty em sistemas baseados em ASCII como Linux normalmente traduzem para 0x0a
  • Quando você pressiona Ctrlsozinho, os terminais não enviam nada, mas se você pressionar com C, os terminais enviam um byte 0x03 que a disciplina da linha tty intercepta para enviar um sinal SIGINT para a tarefa em primeiro plano
  • Quando você pressiona Left, 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), o primeiro dos quais é 0x1b. Por exemplo, dependendo do modo em que está, xtermem sistemas baseados em ASCII, enviará 0x1b 0x4f 0x44 ou 0x1b 0x5b 0x44 ( <ESC>[Aou <ESC>OA).

Então aqui, as perguntas que eu faria seriam:

  1. Você ainda deseja avisar ao usuário se stdin não é um terminal
  2. Se a resposta 1 for sim, você deseja avisar o usuário no terminal ou por meio de stdin/stdout?
  3. Se a resposta 1 for não, você ainda deseja esperar 5 segundos entre cada iteração?
  4. Se a resposta para 2 foratravés do terminal, o script deve ser abortado se não conseguir detectar um terminal de controle ou voltar para um modo não terminal?
  5. Você deseja considerar apenas as teclas pressionadas após emitir o prompt. IOW, se o usuário digitar acidentalmente uma chave antes do prompt ser emitido.
  6. Até que ponto você está disposto a ir para ter certeza de ler apenas os bytes emitidos para um único pressionamento de tecla?

Aqui, presumo que você deseja que seu script seja apenas um aplicativo interativo de terminal e interaja apenas por meio do terminal de controle, deixando 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

Responder2

while true; do
    echo 'Looping, press Ctrl+C to exit'
    sleep 5
done

Não há necessidade de complicar mais 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 readfalhar (por tempo limite), o loop continua.

informação relacionada