dev/null 从哪来的?为什么没它,一半脚本都得出错?
原创 SrvTrantor 2026-10-08 07:15 广东

为什么它不是普通文件,而是一种伪设备?
每个 Linux 系统里都有 /dev/null 这个文件。它大小为 0 字节,但每天都有数 TB 的垃圾输出被倒进这里。而一旦把它删除,cron、ssh 以及你一半的 bash 脚本都会直接瘫痪。下面就来讲讲,这个文件是怎么来的,为什么它不是普通文件,而是一种伪设备,以及有哪些东西依赖着它。
一、位桶和只写存储器
/dev/null 其实有一位物理上的前身:位桶(bit bucket)。在 IBM 的电传打字机上,从纸带和穿孔卡片上打孔冲下来的纸屑会落进一个专门的容器里,大家戏称它为“bit bucket”。没人再需要的东西都被送到那里,而送进去的东西再也不会回来……这很有象征意味,而 IT 人又偏偏喜欢象征,于是这个说法就流传了下来,并进入了编程领域。
例如,1972 年 Signetics 的工程师发布过一份恶搞的 25120 芯片数据手册。按照这份手册,它是一种只写存储器,根本无法从中读出任何内容。
这段是 Signetics 25120 WOM 恶搞数据手册里的经典文字,故意把 MOS 的 P 沟道、N 沟道解释成“毕达哥拉斯式”“拿破仑式”,还编出了一个“neu 沟道”。

我插入这段小小的历史回顾,是想说明:“空设备”的历史早在类 Unix 操作系统中出现之前就已经存在了。其他操作系统也有类似丢弃无用输出的做法:
在 DOS 中,这个设备叫 NUL;
在 OpenVMS 中使用 NL: 这个表示法;
在 AmigaOS 中使用的是 NIL: 处理程序;
而在 IBM 大型机的 JCL 中,类似角色由参数 DD DUMMY 扮演。
二、在 Unix 中的出现
1969 年夏天,肯·汤普森几乎独自待了将近一个月,用 PDP-7 的汇编语言写下了第一版 Unix 操作系统。1973 年 11 月,第四版程序员手册出版时,/dev 目录中赫然写着一条技术说明:/dev/null 是一个空的普通文件。写入它虽然有效,但文件会在磁盘上增长,因此不得不时不时手动清理。
一年后,在第五版中,/dev/null 已被定位为一种特殊的字符设备,其语义是“写入总是成功,任何地方都不保存,而读取立即返回文件末尾”。

后来,这个“位桶”开始发展出自己的家族。再往后,/dev/null 旁边出现了 /dev/zero,它在被读取时会返回一串零字节流。有趣的是,它最初是为了给共享库提供匿名内存,而不是用来清理磁盘。后来又出现了 /dev/full:一个总是回答“没有空间”的设备。在 Linux 中,负责这些设备的是 drivers/char/mem.c 文件。
三、/dev/null 是如何工作的
我通过 ls -l /dev/null 来剖析它:
ls -l /dev/nullcrw-rw-rw- 1 root root 1, 3 ... /dev/null
开头的字母 c 表示 character device:字符设备。它是驱动程序的入口点,而数字 1 和 3 告诉内核,在访问时究竟调用哪个驱动程序、哪个处理函数。
最重要的是,写入 /dev/null 时,数据哪里也不会去(字面意义上的)。drivers/char/mem.c 中的 write_null 函数只是返回 count,告诉程序指定数量的字节已成功写入,尽管它并未对这些字节执行任何操作。而 read_null 函数则返回零,程序会将其视为文件结束或 EOF。
该文件的权限是 666,任何用户都可以读写。这是开发者有意而为之,因为 null 必须始终对所有进程可用。
写入 /dev/null 时无需访问磁盘,因此结果取决于系统调用和数据准备。
此外,注意 /dev/null 不是目录,mv file /dev/null 不会将文件丢弃。该命令实际会覆盖 /dev/null ,造成设备损坏。如果执行完这条命令后,你的正常 null 突然消失了(自己就没了,对,没错),那就该去学 mknod 了。
四、当 /dev/null 消失时会发生什么
稍微岔开一下:在九十年代的《Unix 系统管理恐怖故事集》里,有一个故事讲的是一个管理员删除了 /dev/null,不久之后它被重新创建成了一个普通文件。此后系统里开始出现极其严重的权限问题,而他却弄不明白原因(毕竟是个新手)。
同一本书里还有一个关于清理位桶的妙语:一条错误信息,要求你清理一个根本扔不进任何东西的垃圾桶。

回到正题,在 Shell 的世界里,/dev/null 的经典用法就是将输出送往虚无:
command > /dev/null 2>&1首先 stdout 被送入 /dev/null,随后 stderr 也被重定向到同一处。如果把结构反过来写(2>&1 > /dev/null),错误信息仍会继续往外冒。那么,如果 /dev/null 被删除,非特权进程就会开始报错,而某个特权进程可能会在它的位置创建一个普通文件 null。接着还会出现其他问题:
数据将不再消失;
对其他用户来说,访问权限会变得不合适;
读取也不再会返回文件末尾。
在现代操作系统上,如果 /dev 挂载在 devtmpfs 或 tmpfs 上,这样的文件就会占用内存。而如果 /dev 位于普通文件系统上,那么相应分区的可用空间就会受到威胁。可怕吗?还有更可怕的。
cron 也会出问题。任何输出没有被发送到 /dev/null 的任务,都会给任务所有者发一封邮件;而在没有邮件服务的服务器上,这些邮件会堆积在 /var/mail 中,或者作为错误落进日志里。可以通过 MAILTO="" 关闭发送,而要抑制某条具体命令的输出,靠的正是重定向到 /dev/null。
守护进程也与它相关。脱离终端的过程,传统上会把 stdin、stdout 和 stderr 重定向到 /dev/null。如果这个设备不存在,守护进程就无法正确脱离终端。在 systemd 中,同样的逻辑隐藏在 StandardOutput=null 和 StandardError=null 指令里,而这里指的正是那个 /dev/null。
此外,大量工具在启动时会打开 /dev/null,把它当作空输入的替代。在脚本中,人们用 </dev/null 这一手法来压制那些会挂起等待输入的交互式程序——fsck、某些安装程序,以及会不断提问的 apt。如果没有可用的 /dev/null,这类写法就会变得不可预测,而调试这种问题可不是什么好事。
最后,/dev/null 还与 ssh 形成了一种循环关系:
while read host; dossh $host uptimedone < hosts.txt
它(这种循环)可能会跳过一部分主机,因为 SSH 会读取标准输入,并从 read 所处理的那个文件中取走数据。通常,人们会用 -n 参数或从 /dev/null 重定向输入来解决这个问题,但两种做法都依赖于该文件能否正常工作。
要检查你的 /dev/null 是不是真的存在、还能不能用,可以通过以下方式:
stat -c 'type=%F mode=%a major=%t minor=%T' /dev/null 输出结果应当是 type=character special file mode=666 major=1 minor=3。如果那里显示的是 regular file,那我有坏消息要告诉你,而你刚刚读的这一节就没白读。
顺便说一句,恢复被删除的 /dev/null 可以用以下命令:
sudo mknod -m 666 /dev/null c 1 3mknod 会创建一个设备文件,c 表示该设备是字符设备,1 和 3 分别是主设备号和次设备号。不过顺带一提,在实际运行的系统中,/dev 通常位于 tmpfs 上并由 udev 管理,所以重启之后,这个设备会自己恢复。
重要提示,如果 /dev/null 的位置上已经出现了一个普通文件,那么 mknod 会以 File exists 错误失败。这种情况下,先删除这个不正确的文件:
sudo rm -f -- /dev/nullsudo mknod -m 666 /dev/null c 1 3
之后再次运行 mknod,并检查结果。
五、/dev/null 究竟有什么用
除了压制输出之外,它最常见的用途就是在不删除文件的情况下清空文件。如果某个日志文件还持有打开的文件描述符,就不能删除它,但可以把它清零:
: > /var/log/myapp.log# 或者用老办法:cat /dev/null > /var/log/myapp.log
应用程序会继续写入同一个文件,而空间会被释放。不过,这种做法主要适用于以追加模式打开日志的程序。
另外,/dev/null 作为输入端的“虚空来源”也很方便。例如,GNU tar 能识别出归档被输出到 /dev/null,并为了优化而跳过读取文件内容。如果确实需要读取文件,就应该让归档数据通过管道:
tar -cf - /etc | cat > /dev/null如果需要整个管道的退出码,请输入:
set -o pipefailtar -cf - /etc | cat > /dev/null
顺便也讲讲它的小兄弟:/dev/full,它的行为就像一块已满的磁盘(任何写入都会返回 ENOSPC):
echo test > /dev/fullecho: write error: No space left on device
而 /dev/zero 则在需要一个指定大小的文件,或者要进行彻底擦除时派上用场:
# 一个大小为 1 GiB、填充零的文件dd if=/dev/zero of=test.bin bs=1M count=1024# 将零写入所有可用的逻辑块sudo dd if=/dev/zero of=/dev/sdX bs=1M status=progress
至于第二条命令,想必你也明白,最关键的就是别把 sdX 弄错了。
六、最后
/dev/null 是一个罕见的例子,一个想法的实用性被时间所证明。在我看来,这就是好的抽象该有的样子:0配置、0文档、0意外。
你最常用 /dev/null 做什么?
作者丨SrvTrantor 编译丨dbaplus社群
来源丨网址:https://habr.com/ru/companies/ruvds/articles/1074010/
dbaplus社群欢迎广大技术人员投稿,投稿邮箱:editor@dbaplus.cn

本文转载自微信公众号「dbaplus社群」,查看原文。版权归原作者所有,如有侵权请联系删除。