← [ BACK_TO_NOTES ]

Windows 删除 NUL 等保留设备名文件

解释 Windows 中 NUL、CON、AUX 等保留设备名为何可能成为真实文件,以及使用 \? 扩展绝对路径安全删除和定位创建源的方法。

#howto #evergreen #windows #ops

[!info] related notes

Windows 删除 NUL 等保留设备名文件

核心结论

Windows 中的 NUL 不是普通文件名,而是历史 DOS / Win32 兼容层保留的设备名,代表空设备(null device)。

因此,当 NTFS 上真的出现一个名为 nul 的文件时,会出现一个看起来矛盾的现象:

  • 文件在目录中真实存在;
  • 资源管理器、del nul、部分 PowerShell 文件命令却无法正常操作;
  • 原因通常不是普通权限问题,而是 Win32 路径解析会优先把 nul 解释成设备,而不是目录中的那个文件。

对这种文件,首选做法是使用 \?</code> 扩展路径前缀 + 完整绝对路径,绕过普通 Win32 路径规范化后再删除。

del "\\?\D:\path\to\nul"

如果目标是目录:

rd /s /q "\\?\D:\path\to\nul"

为什么 Windows 会把 NUL 当成特殊名字

Windows 为了兼容 DOS,长期保留了一组特殊设备名。

名称传统含义
CONConsole,控制台
PRNPrinter,打印机
AUXAuxiliary device
NULNull device,空设备
COM1 ~ COM9串口
LPT1 ~ LPT9并口 / 打印端口

这些名字在普通 Win32 文件 API 中会被特殊解析。

例如在原生 CMD 中:

echo hello > nul

不会创建一个叫 nul 的文件,而是把输出丢弃,效果类似 Linux 的:

echo hello > /dev/null

这也是为什么:

del nul

并不一定是在删除当前目录里的真实 nul 文件——路径解析层可能先把它认成设备名。

扩展名也不一定能避开

传统 Win32 规则中,类似下面的名字同样可能命中保留设备名:

nul
NUL
nul.txt
con.log
aux.json
com1.txt

所以不要把“加扩展名”当成可靠规避方案。

为什么磁盘上仍然可能真的出现一个 nul 文件

NTFS 文件系统本身与 Win32 文件名解析规则不是完全同一层。

普通 Windows 应用通常经过 Win32 路径解析,因此不会轻易创建这些名字;但某些跨平台环境、兼容层或更底层的文件操作可以让这种名字真实出现在磁盘上。

最常见原因:跨平台重定向语法混用

一个非常典型的错误是:

some-command > nul

这条命令如果在 CMD 中执行,nul 是 Windows 空设备。

但如果它被 Bash、WSL、Linux CI 或其他 Unix 风格 shell 解释,nul 只是普通相对文件名,于是会在当前目录创建:

./nul

如果当前目录又位于 Windows 的 NTFS 盘上,就可能最终得到一个 Windows 普通工具很难处理的真实 nul 文件。

跨平台脚本应该明确区分:

REM Windows CMD
command > nul
# Linux / Bash / WSL
command > /dev/null

更稳妥的工程实践是使用语言或运行时提供的跨平台 API,而不是在跨平台脚本里硬编码 shell 的空设备名。

其他可能来源

还包括:

  • WSL、Linux 或 Unix 风格工具操作 Windows 挂载目录;
  • Git Bash / MSYS / Cygwin 等兼容环境;
  • 从 Linux/macOS 创建的压缩包中带入特殊文件名;
  • 网络共享、同步程序或第三方文件管理器;
  • 使用 NT 原生路径、扩展路径或更底层 API 的程序;
  • 脚本错误地把 Windows 的 > nul 写法放到了 Unix shell 中。

因此,如果一个项目里的 nul 删除后不断重新出现,应该继续搜索项目脚本,而不是只重复删除。

可以重点搜索:

> nul
2> nul
2>&1 > nul
nul

并检查这些命令实际由 CMD、PowerShell、Bash、WSL 还是其他 shell 执行。

实测删除结果

在一次真实 nul 文件复现中,各方式表现如下:

方法结果
当前目录执行 del nul❌ 报“文件名、目录名或卷标语法不正确”
del ”\?\完整路径\nul”✅ 成功
del ”\.\完整路径\nul”✅ 成功
[System.IO.File]::Delete(”\?…”)✅ 成功
Remove-Item -LiteralPath ”\?…”⚠️ 该环境中返回成功,但文件仍存在

最后一项属于实测环境行为。不同 PowerShell、.NET 和 Windows 版本可能表现不同,因此不应理解成 “Remove-Item 永远无法删除”;但对这种特殊路径,它不适合作为首选方法。

推荐方案一:原生 CMD + \?\

这是最直接、最推荐的方式。

假设文件是:

D:\Projects\MemoFlow\nul

执行:

del "\\?\D:\Projects\MemoFlow\nul"

删除成功时通常没有输出。

然后检查目录:

dir /a "D:\Projects\MemoFlow"

删除同名目录

如果 nul 实际是目录:

rd /s /q "\\?\D:\Projects\MemoFlow\nul"

/s /q 会递归并静默删除目录,使用前必须确认绝对路径正确。

推荐方案二:PowerShell 调用 .NET 文件 API

对于文件:

[System.IO.File]::Delete("\\?\D:\Projects\MemoFlow\nul")

通常不会输出任何内容。

可以用:

[System.IO.File]::Exists("\\?\D:\Projects\MemoFlow\nul")

检查目标是否仍然存在。

对于目录可以使用对应的 Directory API:

[System.IO.Directory]::Delete("\\?\D:\Projects\MemoFlow\nul", $true)

这里的 $true 表示递归删除。

\?\ 和 \.\ 到底是什么

\?\:扩展路径前缀

常见形式:

\\?\C:\path\to\file

它告诉 Windows 文件 API 尽量按给出的路径直接处理,不再执行普通 Win32 路径的很多规范化步骤。

因此它经常用于:

  • 特殊文件名;
  • 普通 Win32 解析有歧义的路径;
  • 某些超长路径;
  • 尾随空格或句点等特殊路径场景。

对于真实存在于 NTFS 上的 nul 文件,这是首选前缀。

\.\:Win32 设备命名空间

例如:

\\.\PhysicalDrive0
\\.\COM1

它更常用于访问设备。

实测中:

del "\\.\D:\path\to\nul"

同样能够删除目标,但从语义上讲,处理普通文件系统特殊路径时更推荐:

\\?\

所以可以把 \.</code> 视为已验证过的备选,而不是首选写法。

三个最容易踩的坑

1. 使用普通相对路径

下面这种写法不可靠:

del nul

即使先切换目录:

cd /d D:\Projects\MemoFlow
del nul

也没有解决核心问题,因为 nul 仍然会经过普通设备名解析。

推荐直接使用:

del "\\?\D:\Projects\MemoFlow\nul"

也就是:

扩展路径前缀 + 完整绝对路径。

2. 把 \?\C:… 命令直接粘到 Git Bash / MSYS

Git Bash、MSYS 等环境会处理反斜杠、盘符和路径转换。

因此:

\\?\C:\...

可能在到达真正的 Windows 命令前就已经被 Bash/MSYS 改写,最终出现“目录名无效”等错误。

所以这种命令应优先在:

  • 原生 CMD;
  • PowerShell。

中执行。

如果确实使用 WSL,则应该使用 Linux 路径语法,例如:

rm -f /mnt/d/Projects/MemoFlow/nul

这种方式在部分环境中也可以直接处理该文件,但它依赖 WSL/DrvFs 的具体行为,不适合作为最通用的 Windows 复制粘贴方案。

3. 看到 PowerShell 返回成功就认为文件已经删除

特殊路径容易出现“命令没报错,但目标还在”的情况。

在上述实测中:

Remove-Item -LiteralPath "\\?\..."

没有报错,但实际文件仍然存在。

因此删除后应该再验证:

dir /a "D:\目标目录"

或:

[System.IO.File]::Exists("\\?\D:\目标目录\nul")

不要只依赖命令返回状态。

如果不知道文件的完整路径

在资源管理器中:

  1. 打开包含 nul 的目录;
  2. 按 Alt + D;
  3. 复制地址栏中的完整目录路径;
  4. 拼接 \nul;
  5. 前面再加 \?</code>。

例如资源管理器路径:

D:\Projects\MemoFlow

最终命令:

del "\\?\D:\Projects\MemoFlow\nul"

路径包含空格时保留双引号。

如果文件删了又出现:定位创建源

删除只能处理结果。如果它再次出现,应定位是谁创建了它。

第一步:搜索脚本中的 nul 重定向

重点检查:

> nul
2> nul
2>&1

特别关注:

  • .bat / .cmd
  • PowerShell 脚本
  • Bash 脚本
  • npm scripts
  • Python subprocess
  • Node child_process
  • Makefile
  • CI/CD workflow
  • Agent / 自动化任务脚本

第二步:确认脚本实际运行在哪个 shell

同样的命令:

command > nul

在不同环境下语义并不相同:

环境nul 的含义
Windows CMDWindows 空设备
原生 Windows 命令环境通常按 Windows 设备名处理
Bash / Linux当前目录普通文件 ./nul
WSL BashLinux 路径语义,可能创建普通 nul
Git Bash / MSYS受 POSIX 兼容层和路径转换规则影响

这就是很多“项目里莫名出现 nul 文件”的根源。

第三步:跨平台脚本不要硬编码空设备

如果程序语言本身提供丢弃 stdout/stderr 的 API,应优先使用 API。

例如 Python:

import subprocess

subprocess.run(
    ["some-command"],
    stdout=subprocess.DEVNULL,
    stderr=subprocess.DEVNULL,
)

这样由 Python 自己映射当前操作系统的空设备,而不是手工写 nul 或 /dev/null。

Node.js 同样可以通过 stdio: “ignore” 等方式避免硬编码路径。

快速操作清单

遇到 Windows 中删不掉的 nul 文件时:

  1. 确认目标确实是真实文件,而不是命令行显示的设备;

  2. 获取完整绝对路径;

  3. 打开原生 CMD;

  4. 执行:

    del "\\?\D:\完整路径\nul"
  5. 如果是目录:

    rd /s /q "\\?\D:\完整路径\nul"
  6. 用 dir /a 再次验证;

  7. 如果文件重新出现,搜索项目中的 > nul 等重定向;

  8. 检查相关脚本到底是在 CMD、PowerShell、Bash、WSL 还是 Git Bash 中执行。

经验结论

这类问题的核心不是“Windows 为什么不给我删除一个文件”,而是:

NTFS 上存在的文件名,与 Win32 路径解析层愿意把这个名字解释成什么,并不是完全相同的概念。

nul、con、aux、prn、com1、lpt1 等名字具有历史兼容语义。

当跨平台工具绕过或不遵循普通 Win32 命名规则,真实文件就可能被创建出来;之后再回到资源管理器、CMD 普通路径或 PowerShell 高层命令中操作,就会撞上设备名解析。

因此解决方案分成两层:

  • 处理现象:用 \?</code> + 绝对路径删除;
  • 修复根因:找到创建它的跨平台脚本,并消除错误的 > nul shell 重定向。
创建于 2026/9/27 更新于 2026/9/27
Digital Biome
Access

键盘导航

j / k / ↑ / ↓
选择对象
Enter / o
打开选择
/ 或 Ctrl+K
全站搜索
i / f / 1–4
筛选当前列表
g → h / l / p / t / a
首页 / 笔记 / 项目 / 工具 / About
r → 1–9
打开所选对象的关联
u
返回上一页
Esc
关闭浮层 / 取消命令;输入框内清空文本