ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Linux大文件切割与合并:split、cat到校验的完整实践指南

Linux大文件切割与合并:split、cat到校验的完整实践指南

1. 先想清楚:大文件为什么要切,以及要付出什么代价

接到这个标题,可能很多人第一反应是:“Linux 下切割文件不就是 split,合并不就是 cat 吗?这也值得写一篇?”说实话,这两个命令确实简单,但实际工作中“切割大文件”这件事远比敲一条命令复杂——切什么类型、按什么粒度切、切完怎么传输、合并完怎么校验,每一环都能挖出不少坑。

我最初碰这个需求,是处理一批生产环境产生的超大日志。单份日志几十 GB,用文本编辑器根本打不开,想传给同事做分析,U 盘和网盘都扛不住,服务器之间的传输也经常中断。后来把文件切成几百 MB 一份,分批发过去再合并,问题就解决了。还有一次是数据库备份文件有十几个 GB,平台上传有单文件大小限制,同样是切分后逐个上传、在目标机器上合并校验。可以说,只要你在 Linux 下跟“大文件”打过交道,迟早都会用到这套操作。

这篇文章适合三类读者:一是刚接触 Linux、想在服务器上处理大文件的开发者和运维;二是需要跨机器或跨平台传输大体积数据的工程师;三是对文件完整性格外敏感,希望每一步都可验证的严谨派。我会从最基础的 split 和 cat 用法开始,讲到分片传输、内容切分、数据校验,最后分享几个我实际踩过的坑。目标很简单:让你的大文件处理过程可复现、可验证、不出事故。

不过先说句实在话:切割确实有成本。文件被拆成多片之后,你就要额外管理“有哪些分片”“分片顺序是什么”“是否全部传输完整”这类元信息。很多事故恰恰是发生在合并阶段——顺序错了、少了一片、传输过程中被截断,最后拼出来的文件损坏还不自知。所以这篇文章的重点不光是怎么切怎么合,还有怎么确认切和合的结果是对的。

2. split 命令:核心参数与三种实战切法

2.1 先记住 split 的基本骨架

split 命令的基本用法是:

split [选项] [输入文件] [输出文件名前缀]

如果不指定输出文件名前缀,默认以x为前缀,生成xaa、xab、xac这样的分片。这个默认后缀看着比较懵,实际使用中几乎所有人都会自定义前缀,比如:

split -b 500M big_file.tar.gz part_

上述命令会把big_file.tar.gz按 500MB 大小切成多份,生成part_aa、part_ab、part_ac……这里part_就是我指定的前缀。参数-b表示按字节大小切割,是使用频率最高的选项。

split 最常用的参数有这么几个:

参数作用适合场景
-b按字节大小切割,支持 K、M、G 等单位二进制文件、压缩包、数据库备份
-l按行数切割日志文件、TSV/CSV 文本
-d用数字后缀代替字母后缀(00、01、02)分片较多时可读性更好
-a指定后缀长度,默认是 2分片数量超过 26×26 时必须调大
--additional-suffix给分片追加额外后缀名给分片加 .part 或 .bin 便于识别

这里的单位换算同样遵循 Linux 惯例:K是 1024 字节,M是 1024² 字节,G是 1024³ 字节。你可能会遇到写kB、MB的旧版本,语义略有差别,建议统一用小写的k、m、g或大写的K、M、G,现在的主流版本都按 1024 计算。

2.2 按字节大小切割:最稳妥的二进制切法

对二进制文件,比如.tar.gz、.zip、.bin、数据库 dump 文件,最常用的就是按字节大小切割:

split -b 200M -d -a 3 --additional-suffix=.part backup.tar.gz backup_

执行后你会看到这样的文件列表:

backup_000.part backup_001.part backup_002.part ...

我解释一下这串参数背后的考虑。-b 200M是主逻辑,每片 200MB;-d -a 3让后缀变成三位数字,从000开始,而不是默认的aa、ab,这样排序和识别更直观;--additional-suffix=.part是给每个分片加上.part扩展名,避免文件类型识别混乱。以后看到backup_000.part,一眼就知道它是备份文件的第一片。

为什么按字节而不是按行数切二进制文件?因为二进制文件没有“行”的概念,按行切只会得到一堆无意义的数据块。而且对二进制文件来说,切片的粒度只影响“切点落在哪里”,不影响内容本身能否被正确拼接。你甚至不需要理解文件内部是什么格式,只要按字节切,合并时按顺序拼接,结果就和原文件完全一致。

2.3 按行数切割:处理日志和文本的优雅方案

如果处理的是日志、CSV、JSON Lines 这类文本数据,按行切比按大小切优雅得多。因为按字节切可能把一个完整行从中间截断,后续处理每行数据时会很麻烦。按行切能保证每行都是完整的:

split -l 100000 access.log access_

上面的命令把access.log按每 10 万行切成一份,得到access_aa、access_ab、access_ac……这种方案在处理文本导入、日志归档时特别实用。

有人会问:我能不能用-b直接切文本?当然能,前提是你只关心文件大小,不关心行完整性。但如果你还要按“行”去 grep、统计,或者写脚本逐行处理,切在行中间就会把一条数据劈成两半,轻则统计出错,重则解析报错。所以我处理文本时,除非是临时传输,否则都优先-l而不是-b。

还有一个容易被忽略的细节:-l后面跟的是行数,不是字节数。对同一天日志,不同时间段行长度差异很大,按行数切出来的分片大小可能相差不少。如果你既要行完整、又要控制单文件大小,实际操作中常是先给出行数上限的粗略估算,切完再检查最大分片,必要时调整参数重新切。

2.4 后缀编号与命名规则:不只为好看,更是为不出错

默认的 split 后缀是两位字母,aa到zz,所以最多只能产生 26×26 = 676 个分片。如果每个分片是 100MB,满打满算才 67GB 左右。一旦文件更大,后缀就会从两位变成三位甚至四位,不同分片的后缀长度可能不一致,排序和识别都很别扭。

所以我的建议是:大文件分片数可能超过 100 片时,直接用-d -a 3生成三位数字后缀;分片数超过 900 时,用-a 4生成四位数字后缀。这样文件名始终形如backup_000.part,配合ls默认的字典序排序也井然有序。

命名规则的另一个作用体现在合并阶段。很多教程直接让你用cat backup_* > backup.tar.gz,这时通配符展开的顺序依赖ls的排序。用数字后缀且位数固定(比如000到099),排序就是天然的 0、1、2……顺序。如果你用了字母后缀且分片数超过 676,新生成的xaaa和已有的xaa在字典序上并不是相邻关系,cat x*合并出来的文件就会乱序。这个问题我在后面第 5 节还会展开说。

3. 合并:顺序、完整性与一条命令的安全性

3.1 cat 合并看着简单,顺序错了就前功尽弃

合并的标准动作是:

cat backup_000.part backup_001.part backup_002.part > backup.tar.gz

或者用通配符:

cat backup_*.part > backup.tar.gz

命令本身没难度,难的是保证“顺序正确”。如果你用cat backup_*.part,Shell 会按文件名展开,而文件名排序按字符逐个比较。backup_0.part、backup_00.part、backup_000.part这几个名字排序结果可能和你直觉不一样。所以我在设计命名规则时一直强调:要么用固定位数的数字,要么干脆在合并脚本里明确写出分片顺序。

这里有一个更隐蔽的问题:通配符展开的顺序受区域设置影响。服务器的LC_ALL或LANG环境变量不同,字符排序结果可能有差异。同一批文件名,在你本地 Mac 上展开是一个顺序,到了 CentOS 服务器上可能是另一个顺序。解决方法是:不要依赖 Shell 展开,在脚本里显式读取分片列表并排序。

ls backup_*.part | sort -V | xargs cat > backup.tar.gz

sort -V是按版本号排序,能正确处理backup_1.part和backup_10.part的顺序关系。如果你的分片全是定长数字后缀,sort就够用;如果历史遗留文件后缀长度不一,sort -V更稳。

3.2 校验:合并完不等于合并对

这是整篇文章我最想强调的一点。文件切割、传输、合并这套流程里,最常见的“成功假象”是:命令执行完毕、文件也生成了、大小看起来也对,但内容已经损坏。

比如分片传输出错,某个分片少了最后几个字节,合并时 Shell 不会报错,cat只管把每个文件读完拼到一起。最后出来的合并文件大小少于原始文件,但你如果不校验根本不会发现。再比如某个分片被别的进程写坏了一部分,大小不变但内容变了,这时候连大小对不上的线索都没有。

所以正确的合并流程,必须包含校验环节。我通常这样做:

  1. 切割前先计算原始文件的校验值:
md5sum big_file.tar.gz > original.md5
  1. 切割、传输、合并完成后再计算合并文件的校验值:
md5sum restored.tar.gz
  1. 对比两个校验值是否一致。

也可以用更安全的 sha256:

sha256sum big_file.tar.gz > original.sha256 sha256sum restored.tar.gz

用diff直接比对原始文件和合并文件也可以,但对超大文件来说,diff需要完整读取两次文件,性能上不如校验值方案。校验值方案最明显的优势是开销小,一条命令就能完成。

3.3 一个完整的分片传输与合并脚本

分享一个我常用的脚本模板。假设原始文件是backup.tar.gz,目标是把分片传到另一台服务器的/data/restore/目录下再合并:

# 在源机器上:切割并计算校验和 split -b 200M -d -a 3 --additional-suffix=.part backup.tar.gz backup_ md5sum backup.tar.gz > backup.md5 # 传输分片(用 rsync 或 scp 均可) rsync -avP backup_*.part backup.md5 user@remote:/data/restore/ # 在目标机器上:合并并校验 cd /data/restore cat backup_*.part > backup_restored.tar.gz md5sum -c backup.md5

md5sum -c backup.md5会读取backup.md5中记录的原始校验值,再对backup_restored.tar.gz计算校验值并自动对比,输出OK或FAILED。这里我刻意把合并文件名写成backup_restored.tar.gz,而不是覆盖掉原始备份名,避免和目标机器上的其他文件混在一起。确认没问题之后,再决定是否改名。

我自己的习惯是再加一步:合并完成后随机抽几个分片做抽查,用head和tail看看分片内容是否符合预期。这条是在文件内容可分读时做的,比如日志和文本。如果是纯二进制压缩包,校验值就够了。

4. 进阶变种:按内容切分、dd 定点切割、管道实时切分

4.1 csplit:不是按大小切,而是按“遇到什么”切

有些场景下,你切文件不想按大小,而是想按内容边界切。比如一份超大的 SQL 导出文件,里面按表分别 dump;或者一个微型小说集合,每章之间有固定分隔符。此时split帮不上忙,要用csplit。

csplit 的典型用法是:

csplit -z -f chapter_ book.txt '/^第.*章$/' '{99}'

这条命令的意思是:在book.txt里查找匹配正则^第.*章$的行,每遇到一次,就在这一行之前把文件切一刀。-f chapter_指定输出前缀,-z表示省略空文件,{99}表示最多执行 99 次切分。如果是按固定行数切,也可以用:

csplit -n 3 -f part_ data.txt 100 200 300

100 200 300是切分点的行号,这样能精确地在第 100 行、第 200 行、第 300 行处下刀。

csplit 的逻辑比 split 复杂,但它解决了一个很实际的问题:对内容敏感的文件,切割点必须是内容边界,而不是字节边界。像前面提到的 SQL dump,如果用split -b切,可能在一行 INSERT 语句中间切断,恢复时只能整体合并用,没法单独拿一片导入调试。用 csplit 按表切,每一片都是独立可用的文件。

4.2 dd 定点切割:低层控制与磁盘镜像场景

如果需要对二进制文件做“从偏移量 X 开始提取 N 个字节”这种精细操作,dd是更合适的工具。比如从损坏的磁盘镜像里提取指定分区区域,或者从大文件中截取某一段验证数据:

dd if=big_file.bin of=part1.bin bs=1M count=100 skip=0 status=progress dd if=big_file.bin of=part2.bin bs=1M count=100 skip=100 status=progress

skip=100表示跳过前 100 个块(每块大小由bs决定),count=100表示读取 100 块。这样两次命令就精确地取出了第 0~99 块和第 100~199 块。合并时还是可以用cat part1.bin part2.bin > full.bin。

dd 的最大优势是“精确”。它不像 split 那样自动连续切分,而是完全由你指定每片从哪里开始、到哪里结束。DD 在磁盘恢复、取证分析、文件系统实验中几乎是标配。但要提醒一句:bs的大小直接影响性能和精确度的平衡。bs=1M适合多数情况,如果切分点不在 1M 的整数倍上,就要调整bs或者用skip、count配合精确字节数,比如:

dd if=big_file.bin of=mid.part bs=1 count=512 skip=1048576

4.3 管道实时切分:不落盘就把数据分开

还有一种常见场景:源文件本身不存在,数据是从某个命令实时输出的。比如从压缩包流式读取并切分:

zcat big_log.gz | split -l 50000 - log_part_

这里zcat解压后的数据直接喂给split,split 按每 5 万行生成一个分片,分片前缀为log_part_。整个过程不需要在磁盘上保留一份完整解压后的文件,既省空间又省 IO。类似地:

tar -cvf - /some/dir | split -b 100M - dir_backup_

把目录打包后直接通过管道切分。管道切分有两个好处:一是避免中间产物占磁盘,二是天然适合流式数据处理管线。不过它也有代价——如果某个分片写坏了,你没法重新生成这一个分片,必须重新跑整个管道。所以重要的管道操作我通常配合tee把校验值也留一份。

5. 我踩过的坑:排序陷阱、后缀溢出与磁盘余量

5.1 文件名排序陷阱:not allcat *are equal

这是个非常容易踩的坑,特别是当分片数量超过 1000 时。假设你切了一个超大文件,分片名为part_aa、part_ab……一直到part_zz之后,出现part_aaa。此时执行cat part_*,Shell 会先展开part_aa、part_ab、……part_zz,然后才是part_aaa。问题来了:part_aaa在字典序里排在part_aa之后还是之前?

答案是:先比较前几个字符,part_aa和part_aaa前五个字符都是part_+aa,但part_aa在这里已经结束,part_aaa还有后续字符,所以part_aaa会排在part_aa后面。粗看问题不大,但如果你的字母后缀不是等长的,某些part_ab和part_aba混在一起,顺序就会乱。最稳妥的方案仍然是我前面说的:用等长数字后缀。一旦用了-d -a 3,文件名就是part_000到part_999,sort或直接文件名展开都是天然正确的顺序。

5.2 后缀长度耗尽:不是文件太大,是后缀不够用

另一个让我印象深刻的教训是:切分一个超过 6.7TB 的文件(默认两位字母后缀),split 直接报错,因为后缀用完了。当时我以为命令写错,实际上就是-a 2最多生成 676 个分片,每片 10GB 就是 6.7TB。

解决办法有两个:一是加-a参数扩大后缀位数,比如-a 4最多可以生成 26^4 = 456976 个分片;二是用-d -a 3这种数字后缀,数字能表示的范围更大。我后来统一用-d -a 3 --additional-suffix=.part,基本上不再操心后缀耗尽的问题。

5.3 合并前必须看磁盘余量:大到没朋友的文件

这个坑很蠢,但我还是踩过。有一次合并一个 50GB 的文件,切分机器上临时目录只有 30GB 可用空间。cat写了一半直接报No space left on device,而且不是原子操作——已经写入的部分会保留为一个残缺文件,你得删掉重来。在处理好大文件时,磁盘余量永远是第一道关卡。

我的习惯是合并前先估算:原始文件大小 * 至少留出 1.1 倍的空间,还要考虑分片本身占用的空间。如果源分片和目标合并文件在同一目录,那需要的空间就是原文件大小 + 分片大小总和。更保险的做法是合并到另一个挂载点,并且在合并脚本开头写一个磁盘空间检查:

required=$(du -sb backup_*.part | awk '{s+=$1} END {print s}') available=$(df --output=avail -B1 /data/restore | tail -1) if [ "$required" -gt "$available" ]; then echo "磁盘空间不足" exit 1 fi

5.4 文本文件按字节切造成的中文乱码

如果日志或文本里有中文,按-b切分时,切点可能落在某个汉字的 UTF-8 编码中间。这在单纯拼接回去时没问题,但如果你单独查看某个分片,或者对分片做 grep、awk 处理,会遇到乱码和异常行。这个现象在我最初处理中文日志时吓了一跳,以为文件损坏了。

解决办法:文本类文件优先用-l按行切。如果一定要按大小切,并且后续还要单独处理每个分片,那就切完做一次字符编码检查,比如用file命令确认每个分片编码正常,或者干脆先把文件转成按行的中间格式再切。对绝大多数业务场景,按行切文本是最省心的。

5.5 分片传输中的完整性:传输工具本身不够

很多人觉得用 rsync 传分片就万事大吉了,确实 rsync 有自己的校验机制,但它校验的是“传输过程是否一致”,解决不了“源文件本身已经损坏”的问题。举例说明:源机器上某个分片已经被写坏了一部分,rsync 照样能把它完整传过去,最后合并出来的文件同样是坏的。

所以正确的流程一定是:源端在切割后立刻生成校验值,传完、合并完以后用校验值对比。把校验值这一步放在切割完、上传前做好,而不是放在合并后。这样一旦发现问题,还能判断是“切出来就是坏的”还是“传输出错了”。

6. 我的日常习惯:一套顺手的切割合并流程

最后分享一套我实际用的工作流。它的优点是每一步都有验证,适合处理重要数据;缺点是命令略多,但基本都是复制粘贴的程度。

第一步,切割前先建立工作目录:

mkdir -p split_work && cd split_work

第二步,切割并生成校验值:

split -b 200M -d -a 3 --additional-suffix=.part /path/to/backup.tar.gz backup_ md5sum /path/to/backup.tar.gz > original.md5

第三步,传输分片。如果两台机器都有 rsync,我用 rsync 并开启压缩:

rsync -avP backup_*.part original.md5 user@remote:/data/restore/

第四步,目标机器上合并并校验:

cd /data/restore && cat backup_*.part > backup_restored.tar.gz && md5sum -c original.md5

第五步,确认无误后,再决定是否删除分片。注意,只有在校验通过之后才删,否则分片一删,原始文件又不在本机,那就真的回不去了。

小文件、临时文件我不建议走这套流程,直接scp或者rsync就够了。真正需要分片的是那些单个文件超过几 GB、传输中断概率高、或者目标系统有单文件大小限制的场景。这套流程我用了很久,几乎没出过问题。

还有一个体验上的小建议:不要把分片大小设得太小。分片越多,文件名管理和传输次数越多,出错的概率反而越高。在平台允许的前提下,100MB~1GB 的分片大小通常是比较舒适的范围,既能抵抗传输中断,又不会产生几百上千个文件。根据你自己的网络条件调一下,不用教条。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进