web-dev-qa-db-ja.com

猫と矢印キー/読み取り

テキストの行を読み、編集中にカーソルを左右に移動できるようにします。

catを呼び出すか、bashでreadを使用し、矢印キーを押すと、カーソルを移動する代わりに^[[A^[[B^[[C^[[Dを取得します。

gnome-terminalおよびmate-terminalbashをローカルで使用します(SSHなし)。

1
kyrill

@ steeldriverのおかげで、カーソルを左右に移動でき、上下矢印を押してもbash履歴が表示されないソリューションが見つかりました。

1つの方法は、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

次に、別のスクリプトまたはシェルからこのスクリプトを呼び出します。

var=`readline 'value: '`

別の方法は、関数を作成することです

この関数はサブシェルで実行するように定義でき、上記のスクリプトと本質的に同じになります。

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

または、現在のシェルで直接実行することもできます。その場合、現在のシェルの履歴をクリアする前に保存し、復元する必要があります。

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
}

したがって、この最後のバージョンは元のバージョンよりも「少し」複雑であり、サブシェルの起動を回避しませんが、読み取りが成功したかどうかの指標を提供します(どちらの単純なバージョンもそうではありません)。

次のように使用できます。

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

readシェルのbashビルトインでは、-eオプションを使用してreadlineサポートを有効にできます。 help readから:

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

例えば

read -ep "Please enter some text: "

catヒアドキュメントでこれを行う方法を知りません。

4
steeldriver