BASH中使用通配符的FOR循环异常问题排查
咱们来一步步拆解你遇到的两个问题:
为什么无匹配文件时脚本会失败?
默认情况下,Bash的通配符(也就是*这种glob模式)如果找不到匹配的文件,不会凭空消失,而是会把通配符本身当成一个字符串传给循环。比如你脚本里的"$input_path/*h3*$year*.gz",当目录里没有符合*h3*1950*.gz的文件时,这个通配符不会被展开,nc_gz变量的值就变成了[你的input_path]/\*h3\*1950\*.gz这个完整字符串。
接下来你执行gunzip $nc_gz的时候,因为没给变量加引号,Bash会尝试再次拆分这个字符串里的通配符,但还是找不到匹配的文件,最后就把这个通配符字符串当作文件名传给gunzip。gunzip找不到这个文件,自然就报错失败了。
为什么偶尔会生成h3 1950 *这种奇怪的文件?
这大概率是没给变量加引号导致的。举个例子:如果你的input_path里包含空格(比如./my data),那么"$input_path/*h3*$year*.gz"会变成./my data/*h3*1950*.gz。当你不加引号执行gunzip $nc_gz时,Bash会把这个字符串按空格拆成./my和data/*h3*1950*.gz两个参数传给gunzip。
gunzip会优先处理第一个参数./my——如果这是个目录或者不存在的文件,就可能触发异常行为,甚至生成奇怪的临时文件。另外,如果通配符被错误拆分后,gunzip接收到不完整的参数,也可能生成这类莫名其妙的文件名。
怎么解决这些问题?
给你两个靠谱的修复方案,选一个就行:
方案1:启用nullglob选项
这个选项会让Bash在通配符找不到匹配文件时,直接移除这个模式,循环根本不会执行,从根源避免报错。修改后的脚本如下:
# 启用nullglob,无匹配时移除glob模式 shopt -s nullglob year=1950 # 注意glob模式的写法:input_path加引号,glob部分在外 for nc_gz in "$input_path"/*h3*"$year"*.gz; do # 给变量加双引号,防止空格/通配符拆分 gunzip "$nc_gz" done # 可选:用完后关闭nullglob,避免影响后续脚本逻辑 shopt -u nullglob
方案2:循环内检查文件是否存在
如果不想修改shell选项,可以在循环里先判断当前的nc_gz是不是真实存在的文件,跳过那些未展开的通配符字符串:
year=1950 for nc_gz in "$input_path"/*h3*"$year"*.gz; do # 检查是否为真实文件,不是就跳过 [[ -f "$nc_gz" ]] || continue gunzip "$nc_gz" done
关键注意事项
一定要记住这两点,能避免90%的Bash文件处理问题:
- 给所有变量加双引号:比如
"$nc_gz"、"$input_path",这样能正确处理带空格的路径/文件名,也不会让通配符被错误拆分。 - 别把glob模式包在引号里:你原来的
"$input_path/*h3*$year*.gz"是错误的,正确写法是"$input_path"/*h3*"$year"*.gz——让$input_path被引号包裹(处理空格),而glob模式在引号外,让Bash正常展开匹配文件。
内容的提问来源于stack exchange,提问作者Fab

