Usando o comando de leitura do shell com funcionalidade de edição ao vivo (como readline)

6

Existe uma maneira padrão (POSIX) de solicitar ao usuário alguns dados de dentro de um script de shell, com read por exemplo, enquanto permite edição ao vivo do texto que está sendo digitado (o que readline faz)?

Eu sei que bash tem read -e varname que permite que a pessoa que lança o script use as setas do teclado para, por exemplo, editar ou corrigir o que acaba de ser digitado sem excluir os últimos caracteres inseridos com backspace.

No entanto, read -e é específico do bash . E, ainda assim, é bastante complicado deletar tudo o que foi escrito se você perceber que cometeu um erro no começo de sua longa sentença ...

    
por Totor 02.02.2014 / 04:09

1 resposta

5

O driver de terminal tem recursos de edição de linha na maioria dos sistemas. Você notará que pode usar Backspace , Ctrl-U , às vezes Ctrl-W .

readline é uma biblioteca GNU mantida ao lado de bash . Não há nada POSIX sobre isso. O POSIX define um editor de linha opcional (com vi key binding) para sh , mas não há provisão para usá-lo fora de sh .

Ele especifica vi (opcional), portanto, você pode invocar um editor vi para editar o conteúdo de um arquivo temporário.

O zsh equivalente de bash read -e é vared (muito mais avançado, pois está usando o zle (editor de linhas zsh) do zsh ).

Em outros shells, você pode usar alguns wrappers em torno da linha de leitura ou de outras bibliotecas de edição de linhas (como rlwrap ) ou pode invocar bash -c 'read -e...' ou zsh -c 'vared...' .

O que você também pode fazer é dar a oportunidade ao usuário de lançar um editor.

Como:

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

Em seguida, o usuário pode pressionar Ctrl-D duas vezes para iniciar um editor sobre o que ele já digitou.

Caso contrário, escrevi uma vez essa função deve funcionar na maioria dos terminais na maioria dos Unices que implementa um editor de linhas simples.

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' "
LE 'Prompt: '
$LE_k") printf '%b%s' "
LE 'Prompt: [....]\b\b\b\b\b' 4 . DEF
$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" }

Para ser usado como:

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

Ou:

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' "
LE 'Prompt: '
$LE_k") printf '%b%s' "
LE 'Prompt: [....]\b\b\b\b\b' 4 . DEF
$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" }

se você quiser um comprimento máximo e / ou um caractere de preenchimento diferente e / ou um valor inicial.

    
por 04.02.2014 / 12:57