我有一个脚本,它带有一个-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)⏎
$
标准
所以:我想要一个单键响应:
- 不会导致在一行上打印多个提示/答案
- ⏎按 接受默认值时不会产生空行
- 仍然保留 :如果键是or 则
read -q
设置answer
为 的行为;否则将其设置为.y
yYn
解决方案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
而不是-q
被传递给,read
以便它改为获得“读取一个字符,任何字符”的行为。这样,我可以测试字符匹配换行符。如果没有 ( $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 选项总是打印一些内容,noecho 似乎很好。如果需要,人们总是可以在 ifecho -n "${answer}"
后面添加read
,并使用某种扩展咒语将换行符变成空白。)
它比第一个解决方案长一行,但可能更容易理解,因为它使用read -q
所以不必针对正则表达式进行测试,并且无条件回显换行符。
但另一方面,它做猴子与终端;我已经将两个命令放置得足够接近,但不应该如此也命令中断的时间窗口很大,会导致终端疯狂,但这仍然是一个问题。 (另外,为了完整起见,我应该添加一个-i
不以非交互方式调用的检查,否则,stty
和read
操作将会失败。)
还有比我提出的两个更好或更惯用的解决方案吗?这似乎是许多“-i
交互式”命令(当然大多数是用 shell 以外的东西编写的)遵循的明显行为。
答案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
设置 的选项$interactive
,请将test
for放在循环$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
不需要shell 模式,因此=~
您可以只使用运算符,或在语句中使用[yY]
[[
=
[yY]
case
- 在你的第一个例子中,逻辑似乎有点扭曲:
[[ "${answer}" == 'n' ]] && continue
do_kill $p
什么时候
if [[ $answer = y ]]; then
do_kill $p
fi
更容易阅读。
答案2
我也想要你所描述的行为,但这并不简单。我写了一个辅助函数:
ask_yes_no() {
read -qs "?$1 (y/N) "
out=$?
print $REPLY >&2
return $out
}
# example usage
for thing in one two three; do
if ask_yes_no "Do thing $thing?"; then
echo "Do thing $thing"
else
echo "Nope"
fi
done
输出示例:
Do thing one? (y/N) n
Nope
Do thing two? (y/N) y
Do thing two
Do thing three? (y/N) y
Do thing three