我想阅读一行文本,并且能够在编辑时左右移动光标。
当我在 bash 中调用cat
或使用read
并按下箭头键时,我得到了^[[A^[[B^[[C^[[D
而不是移动光标。
bash
我在本地使用gnome-terminal
和mate-terminal
(无 SSH)。
答案1
使用shellread
的内置命令bash
,您可以使用-e
选项来启用 readline 支持。来自help read
:
-e use Readline to obtain the line in an interactive shell
例如
read -ep "Please enter some text: "
我不知道有什么方法可以利用cat
此处的文档来做到这一点。
答案2
谢谢@steeldriver,我找到了一个解决方案,它允许左右移动光标,并且在按向上/向下箭头时不显示 bash 历史记录。
一种方法是创建一个 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
然后从另一个脚本或者 shell 调用该脚本:
var=`readline 'value: '`
另一种方法是创建一个函数
该函数可以定义为在子 shell 中运行,使其本质上与上面的脚本相同:
readline() (
history -c
read -ep "$*" var
echo "$var"
)
或者也可以直接在当前shell中执行,这种情况下必须在清除当前shell的历史记录之前保存它,然后恢复:
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)
}
然而,如果您决定在输入文本时按Ctrl+ C,则最终将没有历史记录,因为该功能将在恢复历史记录之前被中断。
解决方案是使用陷阱。在 INT 信号上设置一个陷阱,恢复历史记录,然后“解除”信号的捕获。
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
}
然而,如果 INT 信号上已经设置了陷阱,则只需将其丢弃即可。因此,您必须保存已经存在的陷阱,然后设置新的陷阱,完成工作,然后恢复旧陷阱。
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$/\1/' <<<"$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
}
因此,即使最后一个版本比原始版本“稍微”复杂一些,并且仍然无法避免启动子 shell,但它可以指示读取是否成功(两个简单版本都没有这个功能)。
它可以像这样使用:
my_function() {
...
message=`readline $'\e[1mCommit message:\e[m '` || {
echo "[User abort]" >&2
return 1
}
...
}