web-dev-qa-db-fra.com

Touches fléchées avec chat / lu

Je veux lire une ligne de texte et pouvoir déplacer le curseur à gauche et à droite pendant l'édition.

Lorsque j'appelle cat ou utilise read dans bash et que j'appuie sur les touches fléchées, j'obtiens ^[[A^[[B^[[C^[[D au lieu de déplacer le curseur.

J'utilise bash de gnome-terminal et mate-terminal, localement (pas de SSH).

1
kyrill

Grâce à @ steeldriver, j'ai trouvé une solution qui permet de déplacer le curseur vers la gauche/la droite et ne montre pas l'historique de bash lorsque vous appuyez sur les flèches haut/bas.

Une façon est de créer un 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

Appelez ensuite ce script à partir d'un autre script ou shell:

var=`readline 'value: '`

Une autre façon est de créer une fonction

Cette fonction peut être définie pour s'exécuter dans un sous-shell, la rendant ainsi essentiellement identique au script ci-dessus:

readline() (
  history -c
  read -ep "$*" var
  echo "$var"
)

Ou bien il peut être exécuté directement dans le shell actuel, auquel cas l'historique de ce dernier doit être sauvegardé avant de le vider, puis restauré:

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

Cependant, si vous décidez d'appuyer sur Ctrl+C lors de la saisie de texte, vous vous retrouverez sans historique, car la fonction sera interrompue avant la restauration de l'historique.

La solution consiste à utiliser des pièges. Mettre en place un piège sur le signal INT qui restaure l’historique puis "extrait" le signal.

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
}

Cependant, si une interruption est déjà configurée sur le signal INT, il vous suffira de l’écarter. Vous devez donc sauvegarder le piège déjà existant, puis en créer un nouveau, gérer votre entreprise, puis restaurer l’ancien.

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
}

Ainsi, même si cette dernière version est "légèrement" plus complexe que la version d'origine et n'empêche toujours pas le démarrage d'un sous-shell, elle indique si la lecture a réussi ou non (ce qui n'est le cas pour aucune des versions plus simples).

Il peut être utilisé comme suit:

my_function() {
  ...
  message=`readline $'\e[1mCommit message:\e[m '` || {
    echo "[User abort]" >&2
    return 1
  }
  ...
}
0
kyrill

Avec le read intégré au shell bash, vous pouvez utiliser l'option -e pour activer la prise en charge de readline. De help read:

-e     use Readline to obtain the line in an interactive Shell

Par exemple

read -ep "Please enter some text: "

Je ne connais aucun moyen de faire cela avec un cat ici-document.

4
steeldriver