web-dev-qa-db-ja.com

zshスクリプト単一のキーストロークの読み取りを促す:必要に応じて改行を追加しますか?

-iフラグ(→$interactive)を受け取るスクリプトがあり、設定されている場合は、ターゲットごとにyes/no-default-noの質問をします。たとえば、rm -iなどです。

Zshのread -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
$

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

$ 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の動作を引き続き保持します。キーがだった場合はansweryに設定します y または Y;それ以外の場合は、nに設定します。

解決策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の代わりに-qreadに渡されるため、代わりに「1文字、任意の文字を読み取る」という動作が得られます。そうすれば、改行に一致する文字をテストできます。そうでない場合($answer != $'\n')、欠落している改行を印刷できます。

ただし、$answerは、yまたはnだけでなく、押された任意のキーに設定されます。したがって、yまたはY"${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オプションが常に何かを出力するyes/no質問の場合、noechoは問題ないようです。必要に応じて、readの後にecho -n "${answer}"を追加できます。改行を空白に変える拡張呪文。)

これは最初のソリューションよりも長い行ですが、read -qを使用しているため、正規表現に対してテストする必要がなく、無条件に改行をエコーするため、おそらく理解しやすいでしょう。

しかし一方で、それはターミナルでサルをします。私は2つのコマンドを互いに十分に近づけましたが、コマンドの中断が端末の狂気を引き起こすためにあまりにも長い時間枠があってはなりません。しかし、それはまだ懸念事項です。 (また、完全を期すために、-iが非対話的に呼び出されていないことを確認する必要があります。そうしないと、sttyおよびread操作が失敗します。)

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

2
Trey

更新:

マニュアルから:

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_を設定するオプション_$interactive_を処理する限り、testループの外側の_$interactive_に対してforを取ります。例:

_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
_

また:

  • yまたはYをテストするのに_=~_は必要ありません。これは、_[yY]_がシェルパターンであるため、_[[_では_=_演算子、またはcaseステートメントで_[yY]_を使用する
  • 最初の例では、ロジックが少し歪んでいるように見えます。
_[[ "${answer}" == 'n' ]] && continue
do_kill $p
_

いつ

_if [[ $answer = y ]]; then
  do_kill $p
fi
_

読みやすいです。

2
user391836