shell 的 if 本质是根据命令退出状态分支,0 为真、非 0 为假;[ 是命令而非语法符号,前后必须空格;[[ ]] 是关键字,支持正则、无需引号、内置 &&/||,优于 [ ]];整数校验推荐 [[ $input =~ ^-?[0-9]+$ ]];文件测试统一用 -e,避免 -a。

Shell 的 if 不是编程语言里的“条件语句”,它本质是根据命令退出状态(exit code)做分支——0 为真,非 0 为假。 这一点不厘清,所有语法错误、逻辑反直觉、then 不执行的问题,根源都在这儿。
为什么 [ -f file ] 前后必须有空格
[ 实际上是个命令(等价于 test),不是语法符号。shell 解析时会把 [ 当作可执行程序名,后面紧跟的参数必须用空格分隔,否则就变成一个非法命令。
-
[ -f /etc/hosts ]→ 正确:调用[命令,传入三个参数:-f、/etc/hosts、] -
[-f /etc/hosts]→ 错误:shell 尝试执行名为[-f的命令,找不到,返回 127,if判定为假 - 同理,变量未加双引号可能导致空值塌缩成零参数,触发
[ ]缺少参数报错(exit code 1)
[[ ]] 和 [ ] 的关键区别在哪
[[ ]] 是 bash/zsh 的关键字扩展,[ ] 是 POSIX 兼容的命令。日常脚本中优先用 [[ ]],除非你明确要跑在 dash 或其他极简 shell 上。
- 字符串比较支持
=~正则匹配:[[ $str =~ ^[a-z]+$ ]] - 无需对变量加引号防空格塌缩:
[[ $path = /usr/local ]]安全;但[ $path = /usr/local ]遇到含空格的$path会直接报错 -
[[ ]]内部支持&&/||,不用拆成多个[ ]:[[ -n "$a" && -n "$b" ]] -
[ ]中逻辑与必须写成[ -n "$a" ] && [ -n "$b" ],因为-a/-o已被废弃且行为不一致
判断字符串是否为有效整数该用什么方法
-z、-n、= 都不能验数字。整数校验没有内置单操作符,得靠模式匹配或算术上下文兜底。
- 推荐正则(bash 3.2+):
[[ $input =~ ^-?[0-9]+$ ]]—— 支持负数,拒绝前导零(如012) - 宽松校验(允许前导零):
[[ $input =~ ^-?[0-9]*$ ]] && [[ -n $input ]] - 利用算术表达式失败机制(更隐蔽但有效):
if (( input == input )); then ...,非数字时(( ))返回 1 - 避免用
expr或let:前者已过时,后者对空值敏感且不报错
常见文件测试陷阱:-a 和 -e 真的一样吗
-a 和 -e 在绝大多数现代 shell 中行为一致,都判断路径是否存在(不管类型)。但 -a 是遗留用法,POSIX 已不推荐,部分严格模式 shell(如 dash)可能不支持。
- 统一用
-e:语义清晰,兼容性好,if [ -e "$file" ] - 不要混用
-a作逻辑与:旧写法[ -f "$f" -a -r "$f" ]易出错,改用[[ -f "$f" && -r "$f" ]]或分开写 - 注意
-s(非空文件)和-e的区别:空文件存在但-s为假 - 检查可执行权限用
-x,不是看文件后缀或内容
最常被忽略的是:所有条件判断最终都落在 exit code 上。哪怕你写了 if [[ "a" = "b" ]],真正驱动分支的不是字符串比较结果本身,而是 [[ ]] 这个命令执行完返回的 0 或 1。调试时用 echo $? 看上一条命令退出码,比猜逻辑快得多。










