Lê e processa uma string, char por char, mas permite que o usuário simplesmente edite a entrada

5

Eu quero ler incrementalmente uma linha de entrada do terminal, e permitir ao usuário algumas funcionalidades básicas de edição de linha; INS, DEL, DIREITA, HOME ESQUERDA, FIM, BACKSPACE

Cada vez que a string é modificada, eu quero processá-la, para fazer uma pesquisa de regex incremental de um arquivo de texto.

Essas teclas de edição e outras geram vários caracteres de entrada, que você torna bastante difícil interpretar a entrada, por exemplo, C-Left gera 6 caracteres.

Existe uma maneira simples de obter essa entrada editável de caracteres por caracteres?
Estou especialmente interessado em saber como fazer isso no bash, porque o resto do processamento será bash .. Outras sugestões são bem-vindas também.

Aqui está como eu comecei, mas fica um pouco fora de controle com uma variedade potencial de códigos de controle.

#!/bin/bash
IFS=$'\n' 
while true ;do
  read -n 1 c
  ((${#c}==0)) && break # Exit the loop. Input length is 0  
                        # ie. The user has pressed Enter
  echo "xx=$(echo -n "$c"|xxd -p)="
  # 1b 5b 32 7e  "INS"
  # 1b 5b 33 7e  "DEL"
  # 1b 5b 43     "RIGHT"
  # 1b 5b 44     "LEFT"
  # 1b 5b 46     "END"
  # 1b 5b 48     "HOME"
  # 7f           "BACKSPACE"
done
    
por Peter.O 08.08.2011 / 15:38

3 respostas

3

Se você ler um caractere por vez com read -n , terá que implementar um analisador de sequência de chaves. Você pode criar uma solução lenta e suja que funcione na maioria dos terminais com isso: considere que uma seqüência de escape de tecla de função começa com um caractere de escape e continua com qualquer número de caracteres entre 0-9;[]O seguido por um caractere final não neste conjunto .

Uma maneira melhor de ler a entrada é usar uma biblioteca de entrada adequada. O Bash usa um para seus próprios propósitos ( readline ). Você obtém uma interface limitada declarando suas próprias combinações de teclas com o bind interno; especificamente bind -x para executar um comando shell em um pressionamento de tecla. Devido a essa interface limitada, implementar o que você deseja provavelmente será possível, mas difícil.

O Zsh tem sua própria biblioteca de entrada, zle . Sua interface é muito mais rica que a do bash. Com zle, você pode definir keymaps arbitrários e obter mais acesso aos componentes internos do código shell. Use zle para atribuir funções de shell a comandos definidos pelo usuário zle (chamados widgets), bindkey para criar e preencher seu próprio mapa de teclado e finalmente vared para ler uma linha de entrada usando o mapa de teclas de sua escolha. p>     

por 08.08.2011 / 23:14
2

Você pode colocar o terminal no modo raw após o primeiro read se houver um caractere esc e, em seguida, usar um segundo read para ler e analisar os bytes restantes, se houver algum (cf. BASH trauma de caráter de fuga e veja também arrows.txt ).

#!/bin/bash

# tested on Mac OS X 10.6.8

IFS=$'\n'
old_tty_settings="$(stty -g)"
exec 0</dev/tty

tput smir  # enable insert mode

while IFS="" read -r -s -n1 key; do    # first read (reads only a single byte)

#od -c <<<"$key"
#continue

((${#key}==0)) && break

case "$key" in
  $'
#!/bin/bash

# tested on Mac OS X 10.6.8

IFS=$'\n'
old_tty_settings="$(stty -g)"
exec 0</dev/tty

tput smir  # enable insert mode

while IFS="" read -r -s -n1 key; do    # first read (reads only a single byte)

#od -c <<<"$key"
#continue

((${#key}==0)) && break

case "$key" in
  $'%pre%1')  printf '\r'                          #  ctrl-a
            continue;;   
  $'7')  tput rmir 
            printf "0003[P"          # backspace
            tput smir
            continue;;                          
  $'5')  printf "3[1K"                     # tput el1 does not work on Mac OS X 10.6.8 
            continue;;                           # ctrl-u  (clear to start of line)
  $'\v')    tput el
            continue;;                           # ctrl-k  (clear to end of line)
esac

# if the first char is esc (i.e. \e or 3 respectively)
if [[ "$key" == $'3' ]]; then    

  stty cbreak -echo min 0 time 0   # set raw terminal 

  IFS="" read -r bytes     # second read (reads remaining bytes)

  if [[ ${#bytes} -gt 0 ]]; then

     stty "$old_tty_settings"

     case "${key}${bytes}" in 
       $'3[3~')   tput dch1         # delete one char
                     continue;; 
     esac

     printf "${key}${bytes}"

  else
     stty "$old_tty_settings"
  fi

 else

  #stty "$old_tty_settings"
  printf '%s' "$key"

fi

done

echo

exit 0
1') printf '\r' # ctrl-a continue;; $'7') tput rmir printf "0003[P" # backspace tput smir continue;; $'5') printf "3[1K" # tput el1 does not work on Mac OS X 10.6.8 continue;; # ctrl-u (clear to start of line) $'\v') tput el continue;; # ctrl-k (clear to end of line) esac # if the first char is esc (i.e. \e or 3 respectively) if [[ "$key" == $'3' ]]; then stty cbreak -echo min 0 time 0 # set raw terminal IFS="" read -r bytes # second read (reads remaining bytes) if [[ ${#bytes} -gt 0 ]]; then stty "$old_tty_settings" case "${key}${bytes}" in $'3[3~') tput dch1 # delete one char continue;; esac printf "${key}${bytes}" else stty "$old_tty_settings" fi else #stty "$old_tty_settings" printf '%s' "$key" fi done echo exit 0
    
por 08.11.2011 / 16:34
1

Dê uma olhada na ferramenta de linha de comando seletor .

# usage examples
selector -v -x @ <(find . -maxdepth 2 -type d | awk '{print $0"@cd "$0}')
selector -v -x @ <(grep -E -o 'http[^ ]+' fileWithURLS)
    
por 04.11.2011 / 18:01