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
}
...
}