用 cat / read 箭头键

用 cat / read 箭头键

我想阅读一行文本,并且能够在编辑时左右移动光标。

当我在 bash 中调用cat或使用read并按下箭头键时,我得到了^[[A^[[B^[[C^[[D而不是移动光标。

bash我在本地使用gnome-terminalmate-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
  }
  ...
}

相关内容