将 shell 的 read 命令与实时编辑功能一起使用(类似 readline)

Tot*_*tor 8 shell bash posix readline

是否有标准 (POSIX) 方式从 shell 脚本中询问用户一些数据read,例如,同时允许实时编辑正在键入的文本(readline 是做什么的)?

我知道bashread -e varname,允许什么刚刚输入而不会退格键删除最后输入的字符启动脚本中使用键盘箭为例的人,要修改或更正。

但是,read -ebash 特定的。而且,如果您意识到在长句的开头犯了错误,删除所有已写的内容仍然非常麻烦......

Sté*_*las 8

终端驱动程序在大多数系统上具有行编辑功能。您会注意到有时可以使用Backspace, Ctrl-U, Ctrl-W

readline是一个 GNU 库,与bash. 没有任何关于它的 POSIX。POSIX 为 定义了一个可选的行编辑器(带vi键绑定)sh,但没有规定在sh.

当 stdin 和 stderr 都是终端并且相应的选项已经设置时,ksh93shell 使用该vi-style 行编辑器(也支持emacsgmacs-style)作为其read内置:set -o emacs; IFS= read -r var例如read使用 emacs 样式的行编辑器。

POSIX 确实指定了vi编辑器(可选),因此您可以调用vi以编辑临时文件的内容。

's的zsh等价物是(更高级,因为它使用's zle(zsh 行编辑器))。bashread -evaredzsh

在其他 shell 中,您可以在 readline 或其他行编辑库(如rlwrap)周围使用一些包装器,或者您可以调用bash -c 'read -e...'zsh -c 'vared...'

您还可以做的是让用户有机会启动编辑器。

喜欢:

if ! IFS= read -r var; then
  if [ -n "$var" ]; then
    tmp=$(create_tempfile) # create_tempfile left as an exercise
    printf '%s\n' "$var" > "$tmp"
    "${VISUAL:-${EDITOR:-vi}}" -- "$tmp"
    var=$(cat < "$tmp")
    rm -f -- "$tmp"
  else
    exit 1 # real EOF?
  fi
fi
Run Code Online (Sandbox Code Playgroud)

然后用户可以按Ctrl-D两次以在他已经输入的内容上启动编辑器。

否则,我曾经编写过该函数,该函数应该适用于大多数实现简单行编辑器的 Unices 上的大多数终端。

LE() {
# shell Line Editor. Extremely slow and stupid code. However it
# should work on ansi/vt100/linux derived terminals on POSIX
# systems.
# Understands some emacs key bindings: CTRL-(A,B,D,E,F,H,K,L)
# plus the CTRL-W and CTRL-U normal killword and kill.
# no Meta-X key, but handling of <Left>, <Right>, <Home>, <End>
# <Suppr>.
# 
# Args:
#  [1]: prompt (\x sequences recognized, defaults to "")
#  [2]: max input length (unlimited if < 0, (default))
#  [3]: fill character when erasing (defaults to space)
#  [4]: initial value.
# Returns:
#  0: OK
#  1: od(d) error or CTRL-C hit

  LE_prompt=$1
  LE_max=${2--1}
  LE_fill=${3-" "}

  LE_backward() {
    LE_s=$1
    while [ "x$LE_s" != x ]; do
      printf '\b%s' "$2"
      LE_s=${LE_s%?}
    done
  }

  LE_fill() {
    LE_s=$1
    while [ "x$LE_s" != x ]; do
      printf %s "$LE_fill"
      LE_s=${LE_s%?}
    done
  }

  LE_restore='stty "$LE_tty"
              LC_COLLATE='${LC_COLLATE-"; unset LC_COLLATE"}
  LE_ret=1 LE_tty=$(stty -g) LE_px=$4 LE_sx= LC_COLLATE=C

  stty -icanon -echo -isig min 100 time 1 -istrip
  printf '%b%s' "$LE_prompt" "$LE_px"

  while set -- $(dd bs=100 count=1 2> /dev/null | od -vAn -to1); do
    while [ "$#" -gt 0 ]; do
      LE_k=$1
      shift
      if [ "$LE_k" = 033 ]; then
        case "$1$2$3" in
          133103*|117103*) shift 2; LE_k=006;;
          133104*|117104*) shift 2; LE_k=002;;
          133110*|117110*) shift 2; LE_k=001;;
          133120*|117120*) shift 2; LE_k=004;;
          133106*|117106*) shift 2; LE_k=005;;
          133061176) shift 3; LE_k=001;;
          133064176) shift 3; LE_k=005;;
          133063176) shift 3; LE_k=004;;
          133*|117*)
            shift
            while [ "0$1" -ge 060 ] && [ "0$1" -le 071 ] ||
                  [ "0$1" -eq 073 ]; do
              shift
            done;;
        esac
      fi

      case $LE_k in
        001) # ^A beginning of line
          LE_backward "$LE_px"
          LE_sx=$LE_px$LE_sx
          LE_px=;;
        002) # ^B backward
          if [ "x$LE_px" = x ]; then
            printf '\a'
          else
            printf '\b'
            LE_tmp=${LE_px%?}
            LE_sx=${LE_px#"$LE_tmp"}$LE_sx
            LE_px=$LE_tmp
          fi;;
        003) # CTRL-C
          break 2;;
        004) # ^D del char
          if [ "x$LE_sx" = x ]; then
            printf '\a'
          else
            LE_sx=${LE_sx#?}
            printf '%s\b' "$LE_sx$LE_fill"
            LE_backward "$LE_sx"
          fi;;
        012|015) # NL or CR
          LE_ret=0
          break 2;;
        005) # ^E end of line
          printf %s "$LE_sx"
          LE_px=$LE_px$LE_sx
          LE_sx=;;
        006) # ^F forward
          if [ "x$LE_sx" = x ]; then
            printf '\a'
          else
            LE_tmp=${LE_sx#?}
            LE_px=$LE_px${LE_sx%"$LE_tmp"}
            printf %s "${LE_sx%"$LE_tmp"}"
            LE_sx=$LE_tmp
          fi;;
        010|177) # backspace or del
          if [ "x$LE_px" = x ]; then
            printf '\a'
          else
            printf '\b%s\b' "$LE_sx$LE_fill"
            LE_backward "$LE_sx"
            LE_px=${LE_px%?}
          fi;;
        013) # ^K kill to end of line
          LE_fill "$LE_sx"
          LE_backward "$LE_sx"
          LE_sx=;;
        014) # ^L redraw
          printf '\r%b%s' "$LE_prompt" "$LE_px$LE_sx"
          LE_backward "$LE_sx";;
        025) # ^U kill line
          LE_backward "$LE_px"
          LE_fill "$LE_px$LE_sx"
          LE_backward "$LE_px$LE_sx"
          LE_px=
          LE_sx=;;
        027) # ^W kill word
          if [ "x$LE_px" = x ]; then
            printf '\a'
          else
            LE_tmp=${LE_px% *}
            LE_backward "${LE_px#"$LE_tmp"}"
            LE_fill "${LE_px#"$LE_tmp"}"
            LE_backward "${LE_px#"$LE_tmp"}"
            LE_px=$LE_tmp
          fi;;
        [02][4-7]?|[13]??) # 040 -> 177, 240 -> 377
                           # was assuming iso8859-x at the time
          if [ "$LE_max" -ge 0 ] && LE_tmp=$LE_px$LE_sx \
             && [ "${#LE_tmp}" -eq "$LE_max" ]; then
            printf '\a'
          else
            LE_px=$LE_px$(printf '%b' "\0$LE_k")
            printf '%b%s' "\0$LE_k" "$LE_sx"
            LE_backward "$LE_sx"
          fi;;
        *)
          printf '\a';;
      esac
    done
  done
  eval "$LE_restore"
  REPLY=$LE_px$LE_sx
  echo
  return "$LE_ret"
}
Run Code Online (Sandbox Code Playgroud)

用作:

LE 'Prompt: '
Run Code Online (Sandbox Code Playgroud)

或者:

LE 'Prompt: [....]\b\b\b\b\b' 4 . DEF
Run Code Online (Sandbox Code Playgroud)

如果您想要最大长度和/或不同的填充字符和/或初始值。