linux文件系统(如ext4、xfs)大小写敏感,c#在.net 6+ linux上system.io api天然继承该行为;跨平台需手动实现大小写不敏感查找,并注意file.move的原子性及docker构建中路径大小写陷阱。

Linux 文件系统本身区分大小写
Linux 默认的 ext4、XFS 等文件系统是大小写敏感的,file.txt 和 FILE.TXT 是两个完全不同的文件。C# 运行在 .NET 6+ 的 Linux 上时,System.IO 的绝大多数 API(如 File.Exists、Directory.GetFiles)会直接调用底层系统调用,因此也天然区分大小写——这不是 C# “要不要处理”的问题,而是“它本来就会这样处理”。
真正容易出问题的场景是:你在 Windows 开发,代码里写了 File.Exists("Config.json"),测试通过;部署到 Linux 后,实际文件叫 config.json,结果返回 false,程序崩了。
如何安全地查找大小写不敏感的文件名
没有内置的“忽略大小写查找” API,必须手动枚举后比对。关键点不是“怎么查”,而是“怎么查得既安全又不慢”:
-
Directory.EnumerateFiles比GetFiles更轻量,适合大目录,避免一次性加载全部路径 - 用
Path.GetFileName提取纯文件名再做不区分大小写的比较,别直接对完整路径ToLower()—— 路径分隔符在 Linux 是/,Windows 是\,混用易出错 - 注意编码:如果文件名含非 ASCII 字符(如中文、日文),
StringComparer.OrdinalIgnoreCase仍可靠;但不要用string.Equals(a, b, StringComparison.InvariantCultureIgnoreCase),它受系统区域设置影响,在容器里可能行为不一致
示例:
var targetName = "readme.md";
var dir = "/path/to/dir";
var found = Directory.EnumerateFiles(dir)
.FirstOrDefault(f => string.Equals(Path.GetFileName(f), targetName, StringComparison.OrdinalIgnoreCase));
File.Move 或 File.Copy 时大小写重命名在 Linux 的特殊行为
在 Linux 上,File.Move("a.txt", "A.TXT") 是合法操作,且成功——它不是“重命名”,而是“同 inode 的大小写变更”,系统允许。但这个操作在 ext4 上是原子的,在某些网络文件系统(如 NFSv3)上可能失败或降级为复制+删除。
风险点:
- 如果你的逻辑依赖“文件移动后
File.Exists("a.txt") == false”,那在大小写重命名后该判断会失效(因为旧名已不存在,但你没检查新名) - 多个线程同时对同一基础名做大小写 Move,可能产生竞态(比如都试图改名为
CONFIG.JSON),建议加lock或用唯一临时名中转 -
File.Replace不支持大小写重命名,会抛IOException:“The process cannot access the file… because it is being used by another process.”——这是预期行为,别把它当 bug 处理
跨平台开发时最该盯住的三个地方
大小写问题往往不是运行时报错,而是静默逻辑错误。上线前务必检查:
- 配置文件加载:确认所有
appsettings.*.json、hosting.json等硬编码路径的大小写和实际部署文件严格一致 - 资源嵌入:用
Assembly.GetManifestResourceNames()查看实际嵌入名,Properties.Resources生成的资源名默认 PascalCase,但代码里写成小写就找不到 - Docker 构建上下文:COPY 指令不改变大小写,但如果你在 Windows 写
COPY ./Config/ ./app/config/,而目录实际叫config/,Linux 构建会静默跳过——构建日志里只有 “no source files were specified”,非常难定位
最麻烦的从来不是“Linux 不认大写”,而是“你根本没意识到自己在假设大小写不敏感”。









