単一のキーストロークを読み取るzshスクリプトプロンプト:適切な場合は改行を追加しますか?

単一のキーストロークを読み取るzshスクリプトプロンプト:適切な場合は改行を追加しますか?

-iフラグ(→)を取るスクリプトがあります。設定されて$interactiveいる場合は、各ターゲットとは異なるターゲットについてはい/いいえ - デフォルト - いいえ質問をしますrm -i

Zshread -qはこのような状況のために設計されています。単一のキーを許可し、yキーがまたは場合はy変数をに設定しY、そうでない場合はに設定しますn

私の問題は、プロンプトを繰り返していることです。だからそれ自体同じ行に私のプロンプトを繰り返し印刷します。つまり、このコードは次のようになります。

# moshPids is an array of pids
for p in $moshPids; do
  if [[ -n $interactive ]]; then
    read -q "answer?Kill $p? (N/y)"
    [[ "${answer}" == 'n' ]] && continue
  fi
  # do_kill set to print “killing $1” in debug
  do_kill $p
done

結果:

$ myCmd
Kill 123? (N/y)nKill 456? (N/y)nKill 789? (N/y)yKilling 789
$

次のようにプロンプ​​トに改行を含めるなど、これには多くのソリューションがあります(その一部はこのサイトの回答にあります)。

for p in $moshPids; do
  if [[ -n $interactive ]]; then
    read -q "answer?Kill $p? (N/y)
"
    [[ "${answer}" == 'n' ]] && continue
  fi

  do_kill $p
done

結果:

$ myCmd
Kill 123? (N/y)
nKill 456? (N/y)
nKill 789? (N/y)
yKilling 789
$

これは私にとって醜いようです。別の解決策は、コマンドecho >&2の後に追加することですread。これは少なくとも一見すると完璧に見えます。

$ myCmd
Kill 123? (N/y)n
Kill 456? (N/y)y
Killing 456
Kill 789? (N/y)n
$

ただし、デフォルト値を受け入れると空白行が表示されます(実際の出力には表示されませんが、入力を表示するために追加されます)。

$ myCmd
Kill 123? (N/y)⏎

Kill 456? (N/y)y
Killing 456
Kill 789? (N/y)⏎

$

基準

したがって、ワンクリック応答が必要です。

  • 複数のヒント/回答は1行に印刷されません。
  • 「デフォルト値を受け入れる」をクリックすると空白行は生成されません。
  • キーがまたはの場合にread -q設定する動作を維持し続けます。それ以外の場合に設定します。answeryyYn

ソリューション1

これが私の最初の解決策です。

for p in $moshPids; do
  if [[ -n $interactive ]]; then
    read -k1 "answer?Kill $p? (N/y)"
    [[ $answer != $'\n' ]] && echo >&2
    [[ "${answer}" =~ '[yY]' ]] || continue
  fi

  do_kill $p
done

ここでは、-k1代わりに「1文字、すべての文字を読む」アクションを取得するように-q渡されます。readこれにより、文字が改行文字と一致するかどうかをテストできます。 ($answer != $'\n')がない場合は、行方不明の改行文字を印刷できます。

しかし、今では、または$answerキーだけでなく、押したキーに設定されます。だから我々は()を確認する必要があります。ynyY"${answer}" =~ '[yY]'

この問題を処理するより良い方法はありますか?

ソリューション2

sttyまた、キーボードエコーを一時的に無効にする呼び出しを使用してこれを検討しました。

for p in $moshPids; do
  if [[ -n $interactive ]]; then
    stty -echo
    read -q "answer?Kill $p? (N/y)"
    stty echo
    echo >&2
    [[ "${answer}" == 'n' ]] && continue
  fi

  do_kill $p
done

きれいな出力の優先順位を決めるのか、ロールバック時に入力を見ることができるかに応じて、キーレスポンスの可視性を無効にすると、より良いかもしれません。 (この場合はい/いいえ質問では、yesオプションは常に何かを印刷し、noechoは大丈夫に見えます。必要に応じて常にifのecho -n "${answer}"後に追加し、readいくつかの拡張注文を使用して改行をスペースに置き換えることができます。)

最初の解決策よりも1行長いですが、正規表現をread -qテストする必要はなく、無条件に改行をエコーするので、理解しやすくなります。

しかし、一方では、する猿対ターミナル。両方のコマンドを十分に近づけましたが、そうしないでください。またコマンドの中断の時間ウィンドウは、端末が狂ってしまうのに十分な大きさですが、まだ問題になります。 (また、完全性のために-i非対話型で呼び出されないチェックを追加する必要があります。そうしないと、sttyANDread操作は失敗します。)

私が提案した2つ以上のより良い、より慣用的な解決策はありますか?これは-i、多くの「インタラクティブ」コマンド(もちろん、ほとんどはシェル以外のもので書かれています)が続く明らかな動作のようです。

ベストアンサー1

修正する:

マニュアルから:

read ...

-s Don't echo back characters if reading from the terminal.

効果があります:

for p in $pids; do
  read -qs "?Kill $p? (N/y)"
  >&2 echo $REPLY
  case $REPLY in
    (y) do_kill $p ;;
  esac
done
  • read -q返品状況も提供されます。
for p in $pids; do
  if read -qs "?Kill $p? (N/y)"; then
    >&2 echo $REPLY; do_kill $p  # answer was y or Y
  else
    >&2 echo $REPLY          # answer was not y or Y
  fi
done

あなたが望むなら。


-i設定を処理するオプションについては、forをループの外に$interactive置きます。たとえば、次のようになります。test$interactivefor

interactive_kill() {
  for p in $@; do
    read -qs "?Kill $p? (N/y)"
    >&2 echo $REPLY
    case $REPLY in
      (y) do_kill $p ;;
    esac
  done
}

if [[ -n $interactive ]]; then
  interactive_kill $pids
else
  do_kill $pids   # if do_kill() needs exactly one arg wrap in a for loop
fi

返品:

  • シェルモードをテストする必要yYないので、=~演算子を使用するかステートメントで使用できます。[yY][[=[yY]case
  • 最初の例では、ロジックが少し歪んでいるようです。
[[ "${answer}" == 'n' ]] && continue
do_kill $p

いつ

if [[ $answer = y ]]; then
  do_kill $p
fi

読みやすくなります。

おすすめ記事