Bash GLOBIGNORE 的隐藏副作用:只想排除临时文件,为什么隐藏文件反而出现了

前天 3阅读

一个打包脚本原本用星号收集当前目录内容,后来为了排除临时文件,设置了GLOBIGNORE为*.tmp。临时文件确实消失了,但原先不在清单里的.hidden却出现了。看似只增加一条排除规则,为什么输入范围反而变大?

Bash把GLOBIGNORE设为非空值时,会产生启用dotglob的效果。于是以点开头的普通隐藏名称也参与路径名展开,然后才按GLOBIGNORE里的模式过滤。*.tmp只排除对应后缀,不会把.hidden排除,因此最后的列表可能新增隐藏项。

保存为demo.sh,使用bash demo.sh运行,需提供mktemp、touch、rm和rmdir。本文在GNU Bash 5.2.37实跑;脚本只创建专用临时目录与三个已知文件,退出时仅删除这些演示文件并移除空目录。不要用source执行。

Bash GLOBIGNORE 的隐藏副作用:只想排除临时文件,为什么隐藏文件反而出现了

AI模型生成的概念插图:筛网排除一个碎片时,后方帘幕又露出原先看不见的物件,提示过滤设置同时影响候选范围;不是文件管理器截图。

完整可运行程序

#!/usr/bin/env bash
set -euo pipefail
export LC_ALL=C
set +f
unset GLOBIGNORE
shopt -u dotglob nullglob failglob nocaseglob
work=$(mktemp -d)
trap 'rm -- "$work/keep.txt" "$work/skip.tmp" "$work/.hidden"; rmdir -- "$work"' EXIT
touch -- "$work/keep.txt" "$work/skip.tmp" "$work/.hidden"
cd -- "$work"

files=(*)
[[ ${files[*]} == 'keep.txt skip.tmp' ]]
printf 'initial: %s\n' "${files[*]}"

GLOBIGNORE='*.tmp'
shopt -q dotglob
files=(*)
[[ ${files[*]} == '.hidden keep.txt' ]]
printf 'ignore tmp: %s\n' "${files[*]}"

GLOBIGNORE='*.tmp:.*'
files=(*)
[[ ${files[*]} == 'keep.txt' ]]
printf 'ignore tmp and dot: %s\n' "${files[*]}"

unset GLOBIGNORE
if shopt -q dotglob; then exit 1; fi
files=(*)
[[ ${files[*]} == 'keep.txt skip.tmp' ]]
printf 'after unset: %s\n' "${files[*]}"

本次实际输出

initial: keep.txt skip.tmp
ignore tmp: .hidden keep.txt
ignore tmp and dot: keep.txt
after unset: keep.txt skip.tmp

先看候选,再看排除

initial只有keep.txt与skip.tmp。脚本已明确关闭dotglob并清空GLOBIGNORE,星号不会选入.hidden。LC_ALL=C固定名称排序,数组则保存本次展开的结果,使每阶段都能直接检查成员,而不依赖ls的显示格式。

设置GLOBIGNORE之后,ignore tmp变为.hidden keep.txt。shopt断言确认dotglob已经开启;skip.tmp被排除,.hidden因为不匹配*.tmp而留下。这里没有任何程序新建隐藏文件,它从实验开头就已经存在。

多个排除模式用冒号分开

如果目标是既排除临时后缀,又排除点号开头的名称,可以像第三步一样把.*放进GLOBIGNORE,得到*.tmp:.*。输出只剩keep.txt。这个设置仍可能启用dotglob,只是隐藏候选随后被另一条模式移出结果。

因此“列表里没有隐藏文件”和“dotglob处于关闭状态”不是相同事实。排错时同时记录GLOBIGNORE内容、相关shopt状态与实际数组成员,才能判断名称在哪个阶段消失,而不是看到最终清单正确就认定所有开关都没变化。

清除变量也会再次改变开关

最后unset GLOBIGNORE之后,dotglob被关闭,临时后缀的排除规则也没了,所以after unset再次显示keep.txt skip.tmp。示例用条件显式检查dotglob关闭,避免只凭文件列表反推设置,因为恰好没有隐藏文件的目录无法揭示这项状态。

若一段库函数借用GLOBIGNORE,结束后简单unset并不一定恢复调用者原来的全部环境:调用者可能本来就希望dotglob开启。需要隔离的展开操作可以放进子shell,或分别记录并恢复相关变量与选项;别把“删除临时变量”当作完整环境回滚。

本例是独立Bash脚本,设置不会改变启动它的父shell。它还明确重置nullglob、failglob等可能干扰观察的选项。实际批处理若只接受普通文件,应额外检查文件类型;名称通过模式筛选,只能说明名称合格,不能保证不是同名模式的目录。

验收清单至少保留普通文件、应排除的临时文件和点号开头文件。只测试前两类,会让GLOBIGNORE看起来像单纯的后缀过滤器;加入第三类,才能看清候选集合扩大后再过滤的真实结果。

官方资料核验日期:2026年10月2日。上述输出来自本文完整程序的本地执行,全部断言通过,退出状态为零。

参考资料

GNU Bash官方手册:Filename Expansion与GLOBIGNORE

文章版权声明:除非注明,否则均为云鹊BLOG原创文章,转载或复制请以超链接形式并注明出处。