Posso ler um único caractere de stdin no shell POSIX?

4

Apenas read -r é especificado por POSIX ; read -n NUM , usado para ler NUM caracteres, não é. Existe uma maneira portátil de retornar automaticamente depois de ler um determinado número de caracteres do stdin?

Meu usecase está imprimindo prompts como este:

Do the thing? [y/n]

Se possível, eu gostaria que o programa prosseguisse automaticamente depois de digitar y ou n , sem precisar que o usuário pressionasse a entrada depois.

    
por Josh 26.08.2018 / 17:34

3 respostas

7

Ler um caractere significa ler um byte de cada vez até obter um caractere completo.

Para ler um byte com o toolkit de POSIX, há dd bs=1 count=1 .

Note, no entanto, a leitura de um dispositivo terminal, quando esse dispositivo está no modo icanon (como geralmente é por padrão), somente retorna quando você pressiona Return ( Enter ), porque até lá o driver do dispositivo de terminal implementa uma forma de editor de linhas que permite usar Backspace ou outros caracteres de edição para alterar o que você digita, e o que você digita é disponibilizado para o lendo o aplicativo somente quando você enviar a linha que você está editando (com Retornar ou Ctrl + D ).

Por esse motivo, ksh ' read -n/N ou zsh ' read -k , quando detectam stdin é um dispositivo terminal, coloque esse dispositivo fora do modo icanon , para que os bytes estejam disponíveis para ler assim que forem enviados pelo terminal.

Agora observe que ksh ' read -n n só lê up para n caracteres de uma única linha , ele ainda para quando um caractere de nova linha é lido (use -N n para ler n caracteres). bash , ao contrário do ksh93, ainda faz o processamento de IFS e backslash para -n e -N .

Para imitar zsh ou read -k ksh93 ou read -N1 bash , ou seja, leia um e apenas um caractere de stdin, POSIXly:

readc() { # arg: <variable-name>
  if [ -t 0 ]; then
    # if stdin is a tty device, put it out of icanon, set min and
    # time to sane value, but don't otherwise touch other input or
    # or local settings (echo, isig, icrnl...). Take a backup of the
    # previous settings beforehand.
    saved_tty_settings=$(stty -g)
    stty -icanon min 1 time 0
  fi
  eval "$1="
  while
    # read one byte, using a work around for the fact that command
    # substitution strips the last character.
    c=$(dd bs=1 count=1 2> /dev/null; echo .)
    c=${c%.}

    # break out of the loop on empty input (eof) or if a full character
    # has been accumulated in the output variable (using "wc -m" to count
    # the number of characters).
    [ -n "$c" ] &&
      eval "$1=\${$1}"'$c
        [ "$(($(printf %s "${'"$1"'}" | wc -m)))" -eq 0 ]'; do
    continue
  done
  if [ -t 0 ]; then
    # restore settings saved earlier if stdin is a tty device.
    stty "$saved_tty_settings"
  fi
}
    
por 26.08.2018 / 22:42
2

Citando esta resposta ... isso funciona para mim com o bash:

echo -n "Is this a good question (y/n)? "
old_stty_cfg=$(stty -g)
stty raw -echo ; answer=$(head -c 1) ; stty $old_stty_cfg # Careful playing with stty
if echo "$answer" | grep -iq "^y" ;then
    echo Yes
else
    echo No
fi
    
por 26.08.2018 / 18:21
-1

Outra solução com o dd:

key=$(stty -icanon; dd ibs=1 count=1 2>/dev/null)
    
por 26.08.2018 / 18:49