Bash中[[ -s /dev/stdin ]]为何始终返回非真?
[[ -s /dev/stdin ]]在管道传数据时返回假? 这个场景确实有点反直觉,我来给你掰扯清楚背后的逻辑~
你用echo this is some stdin | ./script.sh给脚本传内容,明明cat /dev/stdin能正常读出数据,可[[ -s /dev/stdin ]]却返回1(假),核心问题出在-s选项的判断逻辑和/dev/stdin的本质上。
核心原因:-s测试的是文件物理大小,而/dev/stdin是特殊字符设备
Bash里的-s FILE选项,判断的是「文件存在且自身物理大小大于0」。但/dev/stdin不是普通的磁盘文件,它是一个字符设备文件——这类文件只是数据传输的通道,本身不存储任何数据,所以它的物理大小永远是0(你可以自己敲ls -l /dev/stdin验证,大小列肯定是0)。
管道传过来的内容,其实存在系统的输入缓冲区里,不是存在/dev/stdin这个设备文件里。所以[[ -s /dev/stdin ]]检查的是设备文件的固有属性,自然永远返回假,哪怕缓冲区里有数据。
至于[[ -e /dev/stdin ]]返回真,是因为这个设备文件确实存在,-e只判断存在性,和内容无关。
那怎么判断标准输入是否非空?不一定非要捕获到变量里
你问是不是必须把/dev/stdin的内容存到变量里再判断?其实有几种灵活的方式可选:
1. 用read试探性读取(不消耗后续数据)
可以用read的-t 0(不等待,立即检查)和-n 1(读一个字符)选项判断,如果能读到字符,说明stdin有数据,还能把读到的字符放回缓冲区,不影响后续命令使用:
if read -n 1 -t 0; then echo "标准输入有数据" # 把读到的字符放回stdin,方便后续命令读取完整内容 echo -n "$REPLY" fi # 后续依然可以正常读取内容 cat
2. 用grep快速检查(会消耗数据,适合只判断不保留的场景)
如果只是想判断stdin是否非空,不需要保留内容,可以用:
if grep -q . /dev/stdin; then echo "标准输入非空" else echo "标准输入为空" fi
grep -q .会检查是否存在任意字符,-q选项让它不输出结果,只要匹配到就返回0(真)。但要注意,这种方式会把stdin的内容全部读完,后续无法再复用。
3. 捕获到变量里(稳妥且保留内容)
如果后续还要使用stdin的内容,把它存到变量里是最稳妥的选择,既可以判断是否非空,又能复用内容:
stdin_content=$(cat) if [[ -n "$stdin_content" ]]; then echo "标准输入非空,内容是:$stdin_content" fi
总结一下
[[ -s /dev/stdin ]]返回假,是因为它检查的是设备文件的物理大小,而非输入缓冲区里的数据。判断stdin是否非空有多种方法,不一定非要存变量,但存变量是兼顾判断和内容复用的最优解。
内容的提问来源于stack exchange,提问作者Rock Hupperts

