为何Bash的while/read -d循环会丢失最后一个元素?
嗨,这个问题其实和read -d的工作机制直接相关,我给你拆解一下~
read -d <分隔符>的逻辑是:从输入流里读取字符,直到碰到指定的分隔符为止,然后把分隔符之前的内容赋值给变量,同时返回**成功状态(0)**让while循环继续。
但如果最后一个元素后面没有你指定的分隔符,当read读到输入末尾(EOF)时,虽然它已经把最后这段内容放进变量里了,但因为没找到指定的分隔符,会返回失败状态(非0),这时候while循环的条件不满足,就直接退出了——循环体根本没机会处理这个已经被赋值的最后元素。
举个直观的例子,假设你的代码是这样的:
while IFS= read -d ',' item; do echo "处理元素: $item" done < test.txt
如果test.txt的内容是apple,banana,orange(最后没有逗号),运行后只会输出:
处理元素: apple
处理元素: banana
而orange已经被read赋值给item了,但因为read返回了非0状态,循环没执行最后一次,所以它被跳过了。
最简单的办法就是在while循环结束后,检查一下item变量是否还有内容,如果有的话单独处理它:
while IFS= read -d ',' item; do echo "处理元素: $item" done < test.txt # 处理最后一个没有尾随分隔符的元素 if [[ -n "$item" ]]; then echo "处理元素: $item" fi
这样运行上面的例子,就能输出三个元素了。
另外还有个小技巧:如果你的输入是可控的,可以在输入流的末尾手动加上一个分隔符,比如用cat test.txt <(echo ',')作为输入,这样read就能读到最后一个分隔符,自然会处理所有元素,不过这种方式不如上面的通用。
最后补充一下和普通read(不带-d)场景的区别:普通read按换行符读取时,即使最后一行没换行,它读到内容后返回非0,但有些写法会用while read line || [[ -n $line ]]来强制执行最后一次循环;而read -d的情况是,内容已经在变量里了,只是循环没触发,所以直接在循环后处理变量就好~
内容的提问来源于stack exchange,提问作者markdrayton

