OSX系统Bash脚本循环中read命令失效问题求助
问题根源:管道创建的子Shell导致
read读取错误输入 你的问题核心出在管道(|)会创建子Shell,而你的两层while read循环都在子Shell中运行:
- 子Shell的标准输入(stdin)被管道的输出(也就是
find命令的结果)占据了 - 当你在内部循环里调用
read -p ...时,它并没有从终端读取用户输入,而是直接从管道的剩余输出里取了一行内容,看起来就像read被“忽略”了
另外,你的代码里还有个小错误:int=int+1不是Bash的整数加法写法,应该改成((int++))或者int=$((int+1)),否则int会变成字符串"int+1",后面的[ "$int" -gt 44 ]会触发语法错误。
解决方案
下面提供两种可行的修复方式:
方式1:用进程替换替代管道(推荐)
进程替换(< <(command))可以让循环在当前Shell中执行,而非子Shell,这样read会默认从终端读取输入,同时变量修改也能保留到父Shell:
declare -i int int=1 # 用进程替换代替管道,循环在当前shell执行 while read FILENAME do # 内部循环同样用进程替换 while read FILENAME2 do if [ "$FILENAME" != "$FILENAME2" ]; then if [ "$(dirname "${FILENAME}")" != "$(dirname "${FILENAME2}")" ]; then if [ "$(basename "${FILENAME}")" == "$(basename "${FILENAME2}")" ]; then ((int++)) # 正确的整数自增写法 if cmp -s "$FILENAME" "$FILENAME2" ; then echo "Match", "$FILENAME", "$FILENAME2", "$int" else read -p "No match modify? (y/n)" d echo "$d" fi if [ "$int" -gt 44 ]; then exit 1 fi fi fi fi done < <(find . -name '*.xyz') done < <(find ./1 -name '*.xyz')
方式2:强制read从终端读取输入
如果你不想改动管道结构,可以给read命令显式指定输入源为终端设备/dev/tty,这样即使在子Shell里,read也会从终端获取用户输入:
declare -i int int=1 find ./1 -name '*.xyz' | while read FILENAME do find . -name '*.xyz' | while read FILENAME2 do if [ "$FILENAME" != "$FILENAME2" ]; then if [ "$(dirname "${FILENAME}")" != "$(dirname "${FILENAME2}")" ]; then if [ "$(basename "${FILENAME}")" == "$(basename "${FILENAME2}")" ]; then ((int++)) if cmp -s "$FILENAME" "$FILENAME2" ; then echo "Match", "$FILENAME", "$FILENAME2", "$int" else # 强制read从终端读取 read -p "No match modify? (y/n)" d < /dev/tty echo "$d" fi if [ "$int" -gt 44 ]; then exit 1 fi fi fi fi done done
注意事项
- 方式1的进程替换是Bash专属特性,macOS的iTerm默认使用Bash,所以完全兼容;如果是POSIX sh环境则无法使用
- 方式2虽然简单,但子Shell里的
int变量修改不会传递到父Shell,不过你的代码里int仅用于计数退出,这个场景下影响不大 - 建议给所有变量引用加上双引号(比如
"$FILENAME"),避免文件名包含空格或特殊字符时触发语法错误
内容的提问来源于stack exchange,提问作者Raymond gsh
相关产品推荐
相关产品推荐

