为何Bash脚本未从stdin读取就直接终止?
问题描述
我正在编写一个Bash版本的zsh-select替代工具,这个函数放在.bashrc里:
bash-select(){ declare -a arr; while read l; do arr+=( "$l" ); done PS3="$1: "; if [[ -z "$1" ]]; then PS3="Choose: "; fi select sel in "${arr[@]}"; do echo $sel; break; done }
运行时发现,select命令没从标准输入读取用户选择就直接以退出码1终止,但select后的echo还能执行。我怀疑是管道读取选项导致stdin没连接到select,想实现这样的效果:
$> ls | bash-select 1) file1 2) file2 3) file3 Choose: 3 file3
解决方案
问题根源是管道占用了标准输入:当使用ls | bash-select时,函数里的while read已经将管道中的所有数据读取完毕,此时stdin处于EOF状态,select自然无法读取用户的交互输入。
解决思路是让select从终端设备(/dev/tty)读取输入,而非继承管道的stdin。修改后的代码如下:
bash-select(){ declare -a arr; # 从管道读取数据到数组 while read l; do arr+=( "$l" ); done # 简化提示语赋值逻辑 PS3="${1:-Choose: }"; # 强制select从终端读取用户输入 select sel in "${arr[@]}"; do # 仅在选择有效选项时输出并退出 if [[ -n "$sel" ]]; then echo "$sel"; break; fi done < /dev/tty }
关键修改说明
- 将
select的输入重定向到/dev/tty,确保它直接从终端获取用户输入,不受管道影响 - 用
${1:-Choose: }简化原有的PS3判断逻辑,当没有传入第一个参数时,默认使用Choose:作为提示 - 增加
if [[ -n "$sel" ]]判断,避免用户输入无效序号时输出空内容
修改后运行ls | bash-select,就能得到你期望的交互选择效果。
内容的提问来源于stack exchange,提问作者Fabian Schneider
相关产品推荐
相关产品推荐

