Linux下C#如何判断路径是否为普通文件?兼容Windows方案
实现与C的S_ISREG等价的C#普通文件判断逻辑(跨平台兼容)
要实现和C语言中stat+S_ISREG完全等价的普通文件判断,同时在Windows上避免File.Exists误判设备节点的问题,需要分平台针对性实现,以下是具体方案:
核心思路
- Unix-like系统(Linux/macOS/FreeBSD等):直接调用系统
stat函数,复用C代码的S_ISREG判断逻辑,确保和原逻辑完全一致。 - Windows系统:通过Win32 API打开文件并获取文件信息,排除目录和设备节点,避免
File.Exists的局限性。 - 跨平台兼容:运行时判断操作系统,自动切换实现,对未知平台提供降级方案。
代码实现
1. Unix平台实现(直接调用stat)
using System.Runtime.InteropServices; public static class UnixFileUtils { [StructLayout(LayoutKind.Sequential)] private struct Stat { public ulong st_dev; public ulong st_ino; [MarshalAs(UnmanagedType.U4)] public uint st_mode; // 省略其他无关字段,仅保留st_mode即可 } [DllImport("libc", SetLastError = true, CharSet = CharSet.Ansi)] private static extern int stat(string path, ref Stat buf); // S_IFMT是文件类型掩码,S_IFREG表示普通文件 private const uint S_IFMT = 0xF000; private const uint S_IFREG = 0x8000; public static bool IsRegularFile(string path) { if (string.IsNullOrWhiteSpace(path)) return false; Stat statBuf = new Stat(); if (stat(path, ref statBuf) != 0) return false; // 调用失败(如文件不存在),返回false return (statBuf.st_mode & S_IFMT) == S_IFREG; } }
2. Windows平台实现(Win32 API判断)
using System.Runtime.InteropServices; public static class WindowsFileUtils { [StructLayout(LayoutKind.Sequential)] private struct BY_HANDLE_FILE_INFORMATION { public uint FileAttributes; public System.Runtime.InteropServices.ComTypes.FILETIME CreationTime; public System.Runtime.InteropServices.ComTypes.FILETIME LastAccessTime; public System.Runtime.InteropServices.ComTypes.FILETIME LastWriteTime; public uint VolumeSerialNumber; public uint FileSizeHigh; public uint FileSizeLow; public uint NumberOfLinks; public uint FileIndexHigh; public uint FileIndexLow; } [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)] private static extern IntPtr CreateFile( string lpFileName, uint dwDesiredAccess, uint dwShareMode, IntPtr lpSecurityAttributes, uint dwCreationDisposition, uint dwFlagsAndAttributes, IntPtr hTemplateFile); [DllImport("kernel32.dll", SetLastError = true)] private static extern bool GetFileInformationByHandle( IntPtr hFile, out BY_HANDLE_FILE_INFORMATION lpFileInformation); [DllImport("kernel32.dll", SetLastError = true)] private static extern bool CloseHandle(IntPtr hObject); private const uint GENERIC_READ = 0x80000000; private const uint FILE_SHARE_READ = 0x00000001; private const uint OPEN_EXISTING = 3; private const uint FILE_ATTRIBUTE_DEVICE = 0x40; // 设备文件标识位 public static bool IsRegularFile(string path) { if (string.IsNullOrWhiteSpace(path)) return false; // 以只读方式打开文件,避免修改 IntPtr hFile = CreateFile( path, GENERIC_READ, FILE_SHARE_READ, IntPtr.Zero, OPEN_EXISTING, 0, IntPtr.Zero); if (hFile == IntPtr.Zero || hFile == new IntPtr(-1)) return false; // 无法打开(如文件不存在、权限不足) try { if (!GetFileInformationByHandle(hFile, out var fileInfo)) return false; // 排除目录 if ((fileInfo.FileAttributes & (uint)FileAttributes.Directory) != 0) return false; // 排除设备节点(如NUL、CON等) return (fileInfo.FileAttributes & FILE_ATTRIBUTE_DEVICE) == 0; } finally { CloseHandle(hFile); // 确保句柄释放 } } }
3. 跨平台封装入口
public static class FileChecker { public static bool IsRegularFile(string path) { if (OperatingSystem.IsWindows()) { return WindowsFileUtils.IsRegularFile(path); } else if (OperatingSystem.IsLinux() || OperatingSystem.IsMacOS() || OperatingSystem.IsFreeBSD()) { return UnixFileUtils.IsRegularFile(path); } else { // 未知平台降级处理:结合File.Exists和Directory.Exists做基础判断 return File.Exists(path) && !Directory.Exists(path); } } }
关键说明
- Unix平台:直接调用
stat函数,和原C代码逻辑完全一致,能准确识别普通文件,排除目录、设备节点、符号链接(stat会自动跟随符号链接到目标文件)。 - Windows平台:通过
CreateFile+GetFileInformationByHandle获取真实文件属性,避免File.Exists将NUL、CON等设备节点误判为普通文件。 - 兼容性:使用
.NET 6+提供的OperatingSystem.IsXXX()做运行时平台判断,无需条件编译;未知平台降级到基础判断,保证可用性。
内容的提问来源于stack exchange,提问作者Jonathan Gilbert
相关产品推荐
相关产品推荐

