Teclas de seta com cat / read

1

Eu quero ler uma linha de texto e poder mover o cursor para a esquerda e para a direita durante a edição.

Quando eu chamo cat ou uso read no bash e pressiono as teclas de seta, recebo ^[[A^[[B^[[C^[[D de mover o cursor.

Eu uso bash de gnome-terminal e mate-terminal , localmente (sem SSH).

    
por kyrill 24.03.2017 / 19:01

2 respostas

0

Graças a @steeldriver , encontrei uma solução que permite mover o cursor para a esquerda / para a direita e não mostra o histórico quando pressiona as setas para cima / para baixo.

Uma maneira é criar um script bash

history -c          # clear history
read -ep "$*" var   # read value using readline,
                    #   display prompt supplied as argument
echo "$var"         # echo the value so it can be captured by the caller

Em seguida, chame esse script de outro script ou shell:

var='readline 'value: ''

Outra maneira é criar uma função

Esta função pode ser definida para ser executada em um subshell, tornando-a essencialmente igual ao script acima:

readline() (
  history -c
  read -ep "$*" var
  echo "$var"
)

Ou pode ser executado diretamente no shell atual, caso em que o histórico do shell atual deve ser salvo antes de limpá-lo e, em seguida, restaurado:

readline() {
  history -w          # write current history to the $HISTFILE
  history -c          # ...
  read -ep "$*" var   # ... same as above
  echo "$var"         # ...
  history -r          # resotre history (read from $HISTFILE)
}

No entanto, , se você decidir pressionar Ctrl + C ao digitar o texto, você acabará sem histórico, porque a função seja interrompido antes de restaurar o histórico.

A solução é usar armadilhas. Configure uma armadilha no sinal INT que restaura o histórico e depois "desconecta" o sinal.

readline() {
  # set up a trap which restores history and removes itself
  trap "history -r; trap - SIGINT; return" SIGINT

  history -w
  history -c
  read -ep "$*" var
  echo "$var"
  history -r

  trap - SIGINT
}

No entanto , se uma armadilha já estiver configurada no sinal INT, você a descartaria. Então você tem que salvar a armadilha já existente, então configure uma nova, faça o seu negócio e então restaure a antiga.

readline() {
  local err=0 sigint_trap orig_trap

  sigint_trap='trap -p | grep ' SIGINT$''

  if [[ $sigint_trap ]]; then
    # A trap was already set up ‒ save it
    orig_trap='sed 's/trap -- \(.*\) SIGINT$//' <<<"$sigint_trap"'
  fi

  # Don't do anything upon receiving SIGINT (eg. user pressed Ctrl+C).
  # This is to prevent the function from exiting before it has restored
  # the original trap.
  trap ':' SIGINT

  # 'read' must be called from a subshell, otherwise it will run
  # again and again when interrupted. This has something to do with
  # the fact that 'read' is a shell builtin. Since 'read' reads a value
  # into variable in a subshell, this variable won't exist in the parent
  # shell. And since a subshell is already used, the history might as well
  # be cleared in the subshell instead of the current shell ‒ then it's
  # not necessary to save and restore it. If this subshell returns a
  # non-zero value, the call to 'read' was interrupted, and there will be
  # no output. However, no output does not indicate an interrupted read,
  # since the input could have been empty. That's why an exit code is
  # necessary ‒ to determine whether the read was interrupted.
  ( history -c
    read -ep "$*" var
    echo "$var"
  ) || {
    # 'read' was interrupted ‒ save the exit code and echo a newline
    # to stderr (because stdin is captured by the caller).
    err=$?
    echo >&2
  }

  # The subshell can be replaced by a call to the above script:
  ## "'which readline'" "$@" || { err=$?; echo >&2; }

  if [[ $sigint_trap ]]; then
    # Restore the old trap
    trap "'eval echo "$orig_trap"'" SIGINT
  else
    # Remove trap
    trap - SIGINT
  fi

  # Return non-zero if interrupted, else zero
  return $err
}

Portanto, mesmo que esta última versão seja "ligeiramente" mais complexa que a original e ainda assim não evite iniciar uma sub-rede, ela fornece uma indicação se a leitura foi bem-sucedida ou não (o que nenhuma das versões mais simples faz) .

Pode ser usado assim:

my_function() {
  ...
  message='readline $'\e[1mCommit message:\e[m '' || {
    echo "[User abort]" >&2
    return 1
  }
  ...
}
    
por kyrill 25.03.2017 / 00:41
4

Com o read embutido do shell bash , você pode usar a opção -e para ativar o suporte readline. De help read :

-e     use Readline to obtain the line in an interactive shell

Por exemplo

read -ep "Please enter some text: "

Não conheço nenhuma maneira de fazer isso com um documento cat here.

    
por steeldriver 24.03.2017 / 19:07