需要临时生成一个指定大小的文件,这个需求在 Linux 的日常操作里出现的频率,比你想象的高得多。我自己就不止一次遇到这类场景:做下载速度测试时找不到体积合适的数据包;给监控系统搞容量演练,需要快速把磁盘占用撑到一个阈值;给团队演示文件系统镜像制作,手边却临时缺一份空白素材。每一次都现查命令实在太浪费精力,所以我干脆把 Linux 下生成固定大小文件的主流方法做了完整梳理。这篇文章会围绕 dd、truncate、fallocate、head -c 这几条路线展开,讲清楚每条命令的行为机制、适用场景以及最容易踩的坑,最后给出一套可以直接复用的完整实操流程。
1. 需求场景拆解:什么时候需要"固定大小文件"
1.1 四类典型需求
按理说,在 Linux 里创建文件太简单了,touch 一下就完事。但"固定大小文件"解决的不是"有没有文件"的问题,而是"文件大小严格可控"和"内容特征可预期"两个问题。我把实际工作中遇到的需求归成四类。
第一类是磁盘性能压测。你想验证一块新硬盘的写入带宽、测试某个目录的 IO 能力,或者给监控系统做一次"磁盘即将写满"的演练,这时候需要一个真实占用物理空间、体积可控的文件。这类场景对内容没任何要求,但对"是否真的写入了磁盘"有严格要求。文件多大,磁盘空间就要被占用多少,这一点不能打折扣。
第二类是模拟业务数据。比如你在搭建一套日志分析系统,需要生成若干体积接近真实业务量的日志文件来测试采集、传输、解析链路;又比如你要测试数据库导入,需要一份接近线上体量的数据文件。这类场景对内容有要求,至少不能是全零的二进制乱码,最好是有一定可读性、高重复度的文本内容。生成方式因此会和第一类完全不同。
第三类是系统底层实验。制作 ext4、xfs 文件系统镜像、创建交换分区文件、测试逻辑卷扩展等操作,往往需要一个精确指定大小的文件作为载体。这类场景更看重"大小精确"和"文件系统行为符合预期",例如交换文件就不能是稀疏的,否则内核会拒绝启用。这类场景通常有配套工具链,文件只是个底座。
第四类是网络传输测试。在 Web 服务或 FTP 目录里放一个特定大小的资源文件,用来验证限速策略、上传下载带宽、断点续传逻辑是否正常工作。文件放在服务器上,内容是什么无所谓,但大小必须准确。如果目标显示 100MB,实际传输却只有 97MB,测试结论就毫无意义。这也是最容易被单位换算坑到的一类场景。
1.2 需求驱动的工具选型总览
这四类场景对工具的要求并不一致,没有哪条命令是万能的。下面这张表是我长期使用的选型口径,后面每一节会展开讲原理。
| 需求特征 | 首选方案 | 备选方案 | 备注 |
|---|---|---|---|
| 只占逻辑大小,不占物理空间 | truncate -s |
dd 配合 seek |
生成稀疏文件,测服务端大小判断够用 |
| 必须真实占用物理空间 | fallocate -l |
dd if=/dev/zero |
fallocate 不兼容时回退 dd |
| 内容必须是随机数据 | dd if=/dev/urandom |
head -c /dev/urandom |
用于压缩率、去重类测试 |
| 内容最好是可读文本 | yes + head -c |
awk 循环输出 | 适合模拟日志和业务数据 |
| 必须用作交换文件 | dd if=/dev/zero |
无 | 不建议用 truncate 或 fallocate |
| 需要格式化成文件系统 | truncate -s 或 dd |
fallocate |
mkfs 会重写数据块,稀疏不稀疏无所谓 |
选型逻辑的分歧点始终在"物理占用"和"逻辑大小"这对关系上。truncate 秒建但不占空间;fallocate 快速分配物理块但依赖文件系统支持;dd 最通用但速度受限于真实的写入吞吐。搞清楚自己到底要哪种行为,比死记命令参数重要得多。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 工具深入解析:四条主流生成路线的原理与取舍
2.1 dd 命令:最经典也最容易出错的路线
dd 可能是大多数人最先学会的生成大文件的工具,它本质上是一个低级复制工具,从输入文件读块,往输出文件写块。生成固定大小文件时,最典型的写法是:
bash复制dd if=/dev/zero of=test.bin bs=1M count=1024 conv=fsync status=progress
这条命令的意思是:以 1MiB 为块大小,读取 1024 块,合计 1024MiB,全部写入 test.bin。if 是输入文件,of 是输出文件,bs 是块大小,count 是块数量,最终文件大小就是 bs × count。从 /dev/zero 读取得到的是全零字节,从 /dev/urandom 读取得到的是随机字节,这也是控制文件内容特征的两个最常见输入源。
用 dd 时有一个习惯我建议长期保留:写完大文件后加 conv=fsync。dd 默认写完就返回,数据并不保证已经落盘,加上 fsync 才会对物理磁盘产生真正的写入,同时也会真实反映写入耗时。另一个实用参数是 status=progress,它会实时打印已写入量、总大小和速度,生成几 GB 级别文件时非常直观。
dd 的坑主要集中在块大小和读取方式上。第一,bs 不能设得过于夸张。比如设置 bs=4G 想一次写 4GB,在多数内核和设备配置下会直接报错,因为单次 read/write 请求的大小受文件系统和驱动限制。更稳妥的做法是设 1M、8M 甚至 64M,配合合理的 count。第二,如果输入不是普通文件而是管道,read 系统调用在管道上不会保证一次返回完整块,就可能出现 count 明明足够但文件大小不足的情况,这时候需要加 iflag=fullblock,这个后面在排查章节会详细展开。
2.2 truncate 命令:瞬时创建背后的"稀疏文件"机制
truncate 的命令非常简洁:
bash复制truncate -s 1G test.bin
执行完一瞬间就返回,ls 一看文件大小确实是 1GiB。它不走"写数据"那条路,而是直接修改文件系统中的 inode 元数据,把文件大小字段改成目标值,必要时在逻辑地址空间里留下空洞。这个机制就是 Linux 文件系统里的稀疏文件。
稀疏文件的典型特征是逻辑大小和物理占用不一致。文件看起来很大,但 du 统计时会发现实际只占用了极少甚至零个块,因为空洞区域在文件系统里没有真正分配数据块。对支持稀疏特性的文件系统(ext4、xfs、btrfs 等)来说,读取空洞区域时代理返回全零,对用户完全透明,但这恰恰是很多人困惑"文件这么大却不占磁盘"的原因。
truncate 还有一个常用的扩展用法,就是修改现有文件的大小。truncate -s +500M file 是在原有大小基础上增加,truncate -s -200M file 是缩减,truncate -s 0 file 是清空文件内容但保留文件本身。这些操作同样是在 inode 层面完成的,速度快到几乎没有感觉。
由此引出关键认知:truncate 适合做"逻辑上够大"的文件,不适合做"物理上够大"的文件。拿它去撑磁盘空间是行不通的,剩余空间不会减少;但拿它快速生成一个指定大小的空壳文件,去测试服务端读取行为、限速配置或下载脚本,倒是非常趁手。
2.3 fallocate 命令:最快落盘,但并非所有文件系统都买账
fallocate 是我在实际工作中使用频率越来越高的一条命令:
bash复制fallocate -l 1G test.bin
它比 dd 快几个数量级,因为走的是文件系统预分配接口。你可以把它理解为,直接在文件系统的块分配映射表里划了一块区域归属这个文件,而不需要像 dd 那样把用户态数据逐块拷贝进设备。结果是文件逻辑大小确实为 1GiB,同时也真实占用了 1GiB 物理空间。
但这东西有几个使用边界。第一个边界在文件系统兼容性上,ext4、xfs、btrfs 这些本地文件系统通常没问题,但某些网络文件系统或某些内核版本驱动不支持预分配接口,执行时会返回 Operation not supported 之类的错误,此时只能退回 dd。第二个边界是"分配不等于初始化",fallocate 出来的块在逻辑上读出来是零,但物理块的内容对用户没有意义,这个行为由文件系统保证。第三个边界是 btrfs 这类带有透明压缩特性的文件系统,fallocate 分配的空间是否会被压缩、是否真实占满磁盘,行为和预期可能有差异,动手前最好先用小文件实测一下。
另外要注意,fallocate 不仅能创建文件,还能用来打洞。fallocate -p -o 偏移量 -l 长度 file 可以把指定区域重新变回空洞,这在生成"中间稀疏"的文件时很实用。不过日常固定大小文件的创建,-l 指定长度就够了。这里的单位后缀同样是 1024 进制,1G 就是 1024MiB,和 dd、truncate 的规则保持一致。
2.4 head -c + 重定向:轻量级生成随机文件的野路子
head 命令本来是用来读文件前面若干行的,但配合 -c 选项后,它就变成了一个精确的字节截断器。用 shell 重定向把它接到目标文件上,就可以生成指定大小的文件:
bash复制head -c 200M /dev/urandom > payload.bin
这条命令会从 /dev/urandom 读取随机数据,只截取前 200MiB 写入文件,效果和 dd 从 urandom 拷贝基本一致。它的价值在于语法直观、容易记忆,适合临时少量生成。head -c 1G /dev/zero > zeros.bin 也可以生成零填充文件,但性能不如 dd,因为 head 和 shell 重定向之间的缓冲区配合不如 dd 精细。
这里有一个非常好用的变体,用来生成"内容可读"的大文本文件。前面提到模拟日志文件,如果直接 dd 生成的是全零二进制,很多日志解析工具未必买账。这时可以请 yes 搭配 head:
bash复制yes "INFO 2025-06-01 10:00:00 test log message for large file generation" | head -c 524288000 > /data/fsize-test/app.log
yes 会不停把同一行文本输送到管道,head -c 读完指定字节数后关闭管道,yes 收到管道破裂信号自动退出。生成出来的文件是纯文本,每一行都是重复内容,大小精确可控,非常适合灌给日志采集或数据解析链路。唯一的副作用是文件最后一行可能被截断,但对绝大多数处理程序没有影响。
3. 实战操作:一套完整测试文件的生成流程
3.1 场景设定:先想清楚要验证什么
先说一个我常用来演示的场景。某测试环境需要模拟一套应用产生业务数据的过程,要求在一台 Linux 服务器的数据目录里构造三类文件:一个约 500MiB 的日志文本文件,用来测试日志采集吞吐;一个约 200MiB 的高熵随机文件,用来测试压缩和传输链路的去重效果;一个 2GiB 的交换文件,用来应对内存不足的演练。整个流程如果方法正确,几分钟就能完成。
动手之前先做规划。日志文件用 yes + head -c 生成,因为内容要尽量接近真实文本;随机文件用 dd + /dev/urandom;交换文件用 dd + /dev/zero,并且明确禁止使用 truncate 或 fallocate。所有文件统一放在 /data/fsize-test 目录下,方便后续统一验证和清理。
3.2 生成日志文本文件
创建目录,然后生成 500MiB 的日志文件:
bash复制mkdir -p /data/fsize-test
yes "INFO 2025-06-01 10:00:00 test log message for large file generation, pad pad pad" | head -c 524288000 > /data/fsize-test/app.log
ls -lh /data/fsize-test/app.log
这里有个细节值得多说一句:为什么写 524288000 而不是 500M?裸数字表示字节数,写 500M 时 GNU head 会按 1024 进制解释成 500 × 1024 × 1024,结果恰好等于 524288000。但如果写 500MB,它又会按 1000 进制计算,两个结果相差约 24MiB。实际工程里很容易因为单位混用导致文件大小对不上,所以我的建议是统一用裸字节数,或者统一用带小写后缀的 1024 进制写法,同一个脚本里绝不混用。
生成之后用 stat 或 ls -lh 验证大小,用 head -n 2 看前几行内容确认可读性。这个文件可以喂给日志采集器,测试它每秒能解析多少条记录,用来评估采集插件的吞吐上限。
3.3 生成随机内容文件
接下来生成 200MiB 的高熵随机文件:
bash复制dd if=/dev/urandom of=/data/fsize-test/payload.bin bs=1M count=200 status=progress conv=fsync
这一步要有点耐心,从 /dev/urandom 读数据本质是生成伪随机序列,速度会受到 CPU 和内核实现的影响,200MiB 在普通机器上大约需要几秒到几十秒。如果想加速,可以把块大小调大,比如 bs=4M count=50,通常吞吐会有提升,但不保证每个平台都更优,需要实测。
生成之后可以顺手算一下哈希,建立"这个文件确实是随机数据"的预期:
bash复制sha256sum /data/fsize-test/payload.bin
如果你连续生成两个相同大小的随机文件,它们几乎不可能有相同的哈希值。这个检查并不必要,但作为第一次使用 urandom 生成文件时的验证步骤,值得体验一次。
3.4 综合案例:创建一份可用的交换文件
接下来给测试机增加一个 2GiB 的交换文件。这个案例比较综合,也是容易翻车的环节。很多教程会指导用 fallocate 甚至 truncate 创建交换文件,但这里有一个大坑:交换文件在 mkswap 和 swapon 阶段对稀疏性和块连续性有要求。如果文件存在大的空洞,swapon 会直接报错或者拒绝启用。fallocate 创建的交换文件在部分文件系统上可以工作,但遇到预分配实现不理想的情况时,翻车概率不小。为了保证可靠,我坚持用 dd 生成真实内容:
bash复制dd if=/dev/zero of=/data/fsize-test/swapfile bs=1M count=2048 status=progress conv=fsync
chmod 600 /data/fsize-test/swapfile
mkswap /data/fsize-test/swapfile
swapon /data/fsize-test/swapfile
第一步生成 2048MiB 文件,chmod 600 是安全要求,交换文件会暴露系统内存内容,权限必须收紧。mkswap 负责把文件格式化成交换格式,最后 swapon 启用。验证用 swapon --show 查看启用列表,或者用 free -h 观察 Swap 总量是否增加。只要看到 Swap 总量涨了 2GiB,就说明这一整套流程真正走通了。
3.5 统一验证与 cleanup
三种文件都生成完之后,做一次集中验证。最有效的组合是 ls -lh 加 du -sh:
bash复制ls -lh /data/fsize-test/
du -sh /data/fsize-test/*
在 ext4 或 xfs 上,日志文件、随机文件、交换文件都真实占用物理空间,du 显示的值应该与 ls 的大小基本吻合。如果哪一步忽然发现 du 远小于 ls,基本可以断定是稀疏文件或透明压缩导致的物理空间未分配,需要回到前面的方法重新生成。验证结束后,如果不需要保留,记得先关掉交换文件再删除:
bash复制swapoff /data/fsize-test/swapfile
rm -f /data/fsize-test/swapfile
直接删还在启用的交换文件,虽然系统不会立刻崩溃,但会造成内核元数据不一致,搞不好会留下一个悬空的交换映射,这种野操作我强烈不建议做。
4. 高频问题排查实录
4.1 为什么文件"看起来很大"却没占多少磁盘
这恐怕是围绕固定大小文件最高频的困惑。现象通常是:truncate 生成了一个 1TB 的文件,ls -lh 显示大小为 1.0T,但 df 和 du 都显示磁盘空间没怎么变化,甚至 du 显示文件占用为 0。这不是系统出 bug 了,而是稀疏文件的正常表现。
在 ext4、xfs、btrfs 这类文件系统上,文件空洞不会真的分配磁盘块。逻辑地址空间和物理块映射之间存在 gap,内核读取空洞时返回全零。truncate 的工作方式恰好就是创建这种空洞。反过来,dd if=/dev/zero of=test bs=1M count=1024 这种写法会老老实实把零字节写入每一个块,磁盘占用是实打实的。
排查这类问题时,我习惯的顺序是:先 ls -lh 看逻辑大小,再 du -h 看实际占用,然后用 stat 看 Blocks 字段。如果块数和大小对不上,就是稀疏文件;如果文件系统是 btrfs 且开了透明压缩,还有可能是内容被压缩导致物理占用变小。判断清楚属于哪种情况,再决定要不要换一种生成方式。
4.2 大小单位换算的坑
不同命令对 K、M、G 的解释不一样,这是最容易让演示现场翻车的细节。GNU coreutils 系的工具(dd、truncate、fallocate、head)在历史上有两种后缀体系:不带 B 的 K、M、G 通常表示 1024 进制,带 B 的 kB、MB、GB 表示 1000 进制。在 dd 里,1K 是 1024,1kB 是 1000;在 truncate 里,-s 1G 表示 1024 的 3 次方字节,而 -s 1GB 表示 1000 的 3 次方字节。
避免被坑的最简单做法,是默认全用裸字节数,或者全部明确加单位。要生成 500MiB,就写 524288000 或 500M;要生成 2GiB,就写 2147483648 或 2G。同一个脚本里绝不混用两套体系。这个原则说起来很小,但所有"文件大小和预期不一致"的翻车现场里,至少三分之一是单位换算问题。尤其是脚本里既出现 100M 又出现 100MB 时,排查过程往往极其痛苦。
4.3 dd 读管道时的 partial read
dd 有一个相当隐蔽的行为:当输入是管道而不是普通文件时,read 系统调用可能只返回较小的数据。例如执行某条管道命令,用 dd 作为接收端,填写 bs=1M count=100,想象中应该得到 100MiB,但实际文件可能只有一部分,而且每次跑结果都可能不一样。原因是管道没有普通文件那种"一次读取必然填满"的保证,read 只能返回当前缓冲区内可读的部分,dd 默认不会等待凑满一整块。
解决办法是加 iflag=fullblock:
bash复制dd iflag=fullblock if=某管道输入 of=out.bin bs=1M count=100
这个标志告诉 dd,必须凑满一整块才计入 count,直到读满指定块数或到达 EOF。从普通文件读取时几乎用不到它,因为普通文件系统的 read 总能把块读满。但如果你的脚本里可能出现管道输入场景,养成随手加这个参数的习惯,能省掉很多诡异问题。
4.4 数据落盘与写入性能的感知偏差
用 dd 生成大文件时,命令几秒就结束,你心头一喜以为磁盘性能非常好,结果 df 一看磁盘空间没涨,或者 sync 之后发现系统卡了很久。前者可能是写进了稀疏区或文件系统压缩层,后者往往是 page cache 在起作用。
dd 默认不强制落盘,写完的数据会先停留在内核 page cache,由后台线程逐步刷入磁盘。如果要做严谨的磁盘写入性能测试,或者必须确认数据已经持久化,就给 dd 加 conv=fsync。这个选项会在所有数据写完后再发起一次 fsync,确认文件和元数据都进入持久化存储,此时 df 里磁盘空间一定会有真实减少。我在做磁盘压测或生成必须持久化的文件时,几乎都会加这个参数。不加的话,你测到的只是 page cache 的接收速度,不是磁盘性能。
5. 写在最后的实操心得
这几条命令用了这些年,帮别人排查问题的过程中我发现,最大的障碍往往不是命令写不出来,而是没有建立"逻辑大小与物理占用"这对概念的区分。建议所有做运维或开发的朋友,都把这一组命令打包成自己的工具箱。我在自己的机器上放了一个简单脚本,交互提示输入目标大小和用途类型,脚本自动在 dd、fallocate、truncate 之间做选择。这样从需求到出文件往往只需几秒钟,也避免了每次手敲命令时再想一遍参数。
另一个心得是别把命令固定死,要结合具体文件系统特性选工具。生产环境如果是 btrfs 且开启了透明压缩,用 dd 生成的零填充文件可能并不占满空间,这是压缩生效的结果,不代表你的压测数据可信。遇到这种环境,改用随机内容文件或者调整压缩策略更稳妥。文件系统的行为永远是测试结论的底层约束,忽略它的代价就是得到一个自己都解释不通的结果。
最后分享一个我长期使用的检查习惯:任何方法生成完文件,都同时看三个数字——ls -l 的逻辑大小、du -h 的物理占用、df 的剩余空间变化量。三者关系一旦出现明显矛盾,就停下来观察是不是稀疏、压缩或 page cache 在捣乱。这个习惯成本极低,但能救回大量本来会出错的折腾现场。搞懂这三条数字背后的含义,Linux 下生成固定大小文件这件事,基本就没有什么能难住你了。
