-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)⏎
$
だから:私は次のような単一キーの応答が必要です:
read -q
の動作を引き続き保持します。キーがだった場合はanswer
をy
に設定します y または Y;それ以外の場合は、n
に設定します。これが私の最初の解決策です:
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
の代わりに-q
がread
に渡されるため、代わりに「1文字、任意の文字を読み取る」という動作が得られます。そうすれば、改行に一致する文字をテストできます。そうでない場合($answer != $'\n'
)、欠落している改行を印刷できます。
ただし、$answer
は、y
またはn
だけでなく、押された任意のキーに設定されます。したがって、y
またはY
("${answer}" =~ '[yY]'
)を確認する必要があります。
これを処理するためのより良い方法はありますか?
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
」コマンド(もちろん、ほとんどがシェル以外で記述されている)が従う明らかな動作のようです。
マニュアルから:
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
_
読みやすいです。