☰
Linux split命令详解:大文件切分、日志与CSV处理实战
2026/10/22 15:45:54 网站建设 项目流程

只要处理过,就知道把大文件切分是一件绕不过去的事:一份几十GB的导出数据本地打开就卡死,一台机器往另一台机器传一个文件要传半小时,上游系统对单文件大小还有限制。之前我在某数据处理团队做支持,几乎每周都要和这类文件打交道,最常用的工具始终是这个看起来不起眼的 split 命令。今天这篇就把它剥开揉碎,把 split 的所有选项、适用场景、实际踩坑点和背后的设计逻辑都讲清楚,希望能帮你少走弯路。

适合看这篇的人主要分三类:一是日常要处理日志、导出数据、备份包的运维和 DBA;二是做数据分析、把大 CSV 喂给脚本或 Excel 的同事;三是刚接触 Linux 命令行,想知道一个命令到底能玩出多少花样的学习者。不管哪种,只要你手上有一台带 GNU coreutils 的 Linux 机器,下面这些命令基本都能直接抄。

1. 到底什么场景才需要把大文件切分

1.1 最常遇到大文件切分的四个场景

先讲场景,是因为很多人一上来就背参数,结果背了一堆,真遇到问题时还是不知道怎么选。从我的经验看,切分文件的需求就那么几类:

第一类是日志归档和分发。生产环境里的应用日志一天能涨到几个GB,想用 vim 打开、用 grep 全量扫描都很痛苦;把日志按时间或按固定大小切成小块后,既能快速定位问题,也能让后续的压缩、上传、备份更从容。

第二类是数据库导出和报表分发。很多数据仓库导出 CSV 时没有自动分片功能,一张表几千万行,导出来十几个GB。直接丢给 Excel 超过行数上限,导入数据库又有单文件限制,切成几十个小文件就成了刚需。

第三类是网络传输和跨系统交接。两台服务器之间的传输通道经常限制单文件体积,走对象存储也常有上传上限。相比压缩一个大文件再传,切成固定大小的分片上传,失败后只需要重传单个分片,成本低很多。

第四类是并行计算前的预处理。MapReduce 也好,自己写的多进程脚本也好,都希望把输入切分成大致均匀的 N 份,然后并行处理。split 在这里不是可有可无的工具,而是整个流程的起点。

1.2 三种切分维度:按容量、按行数、按份数

split 命令最核心的设计,是给你三种切分维度:按字节数切、按行数切、按输出文件份数切。这三种维度解决的问题完全不同。

按字节数(-b)切分,输出的每个文件大小基本一致,文件大小可控。适合二进制文件、压缩包、以及只需要“大块搬运”的数据。缺点是如果按字节硬切,文本行的边界会被切断,一个完整的行可能被拆到两个文件里。

按行数(-l)切分,输出的每个文件包含指定行数,适合文本类数据,比如日志、CSV、SQL 脚本。这种切分保留了行的完整性,合并回去也不会错位,缺点是文件大小不一定均匀,因为每行长度差异可能很大。

按份数(-n)切分,你告诉 split“我要 N 份”,它自己计算怎么分。这个选项是 GNU 扩展,底下还有好几个细分模式,后面我会专门讲。它的好处是你不用估算文件总量,直接按并行任务的 Worker 数量来定份数。

1.3 切分之前先想明白的三个问题

动手切分之前,我强烈建议你先想清楚三个问题,否则很容易切出“能看不能用”的文件。

第一个问题:这个文件的边界是什么?如果是文本,保留行边界最重要,那就用-l或-n l/N;如果是二进制,边界无所谓,那就用-b或-n N。

第二个问题:切完之后还要不要合并?如果要合并,必须保证切分规则是可逆的。按行切、按字节切,只要顺序对,用cat合并就能还原;但如果你在中间做了压缩、改了编码、加了表头,合并逻辑就变了。

第三个问题:后续处理工具对文件名有没有要求?有些程序要求文件名必须按顺序排列,有些要求后缀必须是.log、.csv,这决定了你要不要用-d数字后缀和--additional-suffix。

把这三个问题想清楚,再回来看后续的选项详解,你会觉得每个参数都有它的位置。

2. split 命令的完整选项地图

2.1 基本语法与默认行为

split 的基本语法非常简单:

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

有两个容易忽略的细节。第一,如果不给输入文件,split 默认从标准输入读取,这意味着你可以把 split 接到管道中间,对数据流做边读边切。第二,第三个参数“前缀”是输出文件名的开头部分,默认前缀是x,所以直接执行split bigfile,你会得到xaa、xab、xac这样的文件。

后缀默认是两个字母,从aa开始,到zz结束。如果你切分的文件特别多,超过 26×26 个,就需要用-a把后缀长度加长,或者用-d改成数字后缀。这个坑我在第 2.3 节详细讲。

理解默认行为对调试很有帮助。比如你看到一堆xaa、xab命名的文件时,就知道是没指定前缀。我一般会把前缀写成包含目录和业务名的形式,比如parts/sales_,这样输出文件就是parts/sales_aa、parts/sales_ab,一眼能认出是哪个任务的产物。

2.2 按字节和按行切分:-b 与 -l

按字节切分的完整写法是这样:

split -b 100m data.tar.gz part_

这条命令会把data.tar.gz切成多个 100MB 大小的分片,文件名分别是part_aa、part_ab,以此类推。这里的m是单位后缀,常用单位有k、M、G,对应 1024 进制;如果你写MB、GB,在 GNU 版本里会被当成 1000 进制。换句话说,-b 100M实际是 104857600 字节,而-b 100MB是 100000000 字节。这个区别平时不注意没事,一旦你严格按存储容量去规划分片时,就得用ls -l或stat确认实际体积。

按行数切分则是:

split -l 50000 app.log part_

每个输出文件包含 50000 行。需要注意,split 的行是被换行符\n界定的;如果最后一个文件最后一行没有换行符,它依然会被算作一行并写进文件。这个行为符合预期,但如果你用wc -l去统计输出文件行数之和,可能会和原文件差一行,因为wc -l本身也按换行符计数。实际上,我遇到过不止一次这种“行数对不上”的疑问,答案往往不是命令出错,而是原文件结尾没换行。

到底用-b还是用-l,我的经验总结成一句话:二进制文件一定用-b,文本文件优先用-l,除非你有明确的大小控制需求。日志类文件我几乎不用-b,因为按字节硬切会把一条日志从中间断开,后面排查时非常难受。

2.3 控制输出文件编号与后缀:-a、-d、--additional-suffix

默认的字母后缀看着干净,但处理超过几百个分片时就不够用,而且字母排序在脚本里容易被误用。GNU split 提供了一组后缀控制选项。

-a指定后缀长度:

split -b 10m -a 4 data.bin part_

这样生成的文件名是part_aaaa、part_aaab,后缀长度是 4 个字符。

-d把字母后缀改成数字后缀:

split -l 100000 -d data.csv part_

生成part_00、part_01、part_02。配合-a 3就是part_000、part_001,最多支持 1000 个分片。GNU 版本还支持--numeric-suffixes=100,从 100 开始编号,适合你希望分片编号与业务编号对齐的场景。

--additional-suffix是给输出文件名追加定制后缀,日常使用率非常高:

split -l 100000 -d -a 3 --additional-suffix=.csv data.csv part_

生成的文件是part_000.csv、part_001.csv。不加这个选项,你会得到一堆没有扩展名的文件,后续要么自己写循环去重命名,要么在导入工具里手工识别文件类型。

这里提醒一句:这个选项是 GNU 扩展,macOS 自带的 BSD split 并不支持。如果脚本要在 macOS 上跑,要么放弃这个选项,要么安装 GNU coreutils 后使用gsplit命令,后者和 Linux 的行为完全一致。跨平台问题我会在第 6 节展开,这里先有个印象。

2.4 按份数切分:-n 与它的几个变体

-n是 GNU 扩展,也是我切大数据时最常用的选项之一。它解决的核心问题是你不知道文件多大、每行多长,但你知道下游有多少个并发任务。

最简单的用法:

split -n 4 big_file.md part_

这条命令会把文件大致均分成 4 份。注意,是“大致”,因为-n先按字节总量做估算,在文件中间切断,可能把行切断。

如果你希望切分点落在行边界,用l/前缀:

split -n l/4 big_data.csv part_

l/4表示按行均匀分。它会在保证行完整的前提下,尽可能让 4 个文件的大小接近。这个模式非常适合 CSV 和日志。

如果你有 8 个 CPU 核心,想开 8 个进程并行处理,那直接-n l/8就很顺手。还有r/N模式,GNU 文档里叫轮询分配,输出文件的行数可能不均衡,但对某些需要流式处理而不强调大小均匀的场景有用。老实说,日常我用得最多的是-n N和-n l/N两种,其他模式了解即可。

2.5 边切分边处理数据流:--filter

--filter是 GNU split 里非常强大的选项。它的意思是:split 每生成一个输出文件,不直接写盘,而是交给后面的命令处理。命令里用$FILE代表输出文件名。

一个经典场景是切分后立刻压缩:

split -b 50M --filter='gzip > $FILE.gz' backup.sql part_

执行完之后你得到的是part_aa.gz、part_ab.gz。注意,这里不会生成part_aa这种中间文件,分片数据直接流进了 gzip。这比先切完再一个个压缩省了一轮磁盘读写。另一个场景是把每个分片传递给一个上传脚本:

split -l 10000 --filter='/opt/bin/upload.sh $FILE' data.txt part_

只要你的upload.sh能接受文件路径参数,整个过程就变成了一条流水线。我会在第三节给出结合 gzip 的完整实例。

2.6 调试辅助与缓冲控制:-v、-u

-v或--verbose会打印每个输出文件的名字,便于脚本日志里追踪分片进度。这个选项在测试阶段很有用,但在生产脚本里输出会很多,需要搭配重定向使用。

-u或--unbuffered会把输出缓冲调小,每写一条记录就刷新一次。这个选项主要用于与--filter配合处理流式数据,比如从管道读入时你希望下游立刻看到部分结果。正常切文件时不需要加,反而会拖慢速度。

3. 实操一:日志文件批量切分与并行处理

3.1 按大小切分日志并保留清晰的文件名

生产环境里最常见的动作是把大日志按大小切分,方便传输和回溯。带上前缀、数字后缀、扩展名三个要素:

split -b 95M -d -a 3 --additional-suffix=.log app.log log_part_

为什么用 95M 而不是 100M?这是经验值。很多传输工具、压缩工具对 100MB 有默认限制,95M 留了 5% 余量,实际体积仍在一个安全区间;同时-b 95M用的是 1024 进制,实际字节数约 99.6 百万字节,接近但不超过 100MB 的十进制限制。当然,如果传输通道限制明确,直接按目标值算就行。

切完之后,目录里出现log_part_000.log、log_part_001.log。用通配符就能循环处理:

for f in log_part_*.log; do gzip "$f" done

这里有个容易忽略的坑:文件名里带了.log后,循环匹配log_part_*.log不会误匹配到压缩后的.gz文件,但如果先压缩再循环,通配符就要改成*.gz,逻辑容易乱。我个人的习惯是:先用通配符收集文件列表,再统一处理,不要在循环过程中改变匹配规则。

3.2 用 --filter 边切分边压缩

生产日志往往要归档,直接切完不压缩会占很多空间。按我的经验,用--filter一步到位是最省事的方式:

split -b 95M --filter='gzip > $FILE.gz' app.log archive/access_

这个命令会生成archive/access_aa.gz、archive/access_ab.gz。中间不会落地未压缩的分片,CPU 和磁盘都省了。如果你觉得文件名需要数字编号,加上-d -a 3:

split -b 95M -d -a 3 --filter='gzip > $FILE.gz' app.log archive/access_

生成archive/access_000.gz。需要注意,$FILE在单引号里不会被 shell 展开,是 split 内部把它替换成输出路径的;所以--filter的命令必须用单引号,否则会变成空字符串,直接写出个名为.gz的文件。这个细节我第一次用的时候踩过,确认过眼神,是个值得记下的坑。

3.3 切分后并行处理的分寸

切分只是手段,并行处理才是目的。最简单的并行处理方式是用 shell 后台任务:

for f in archive/access_*.gz; do process_one "$f" & done wait

这个写法在文件数量不多时够用,但一把梭开几十个进程很容易把机器打满。更可控的方法是配合xargs:

ls archive/access_*.gz | xargs -P 4 -I {} process_one {}

-P 4表示同时跑 4 个进程。为什么是 4 不是 8?取决于机器 CPU 核数和每个任务的内存占用。我在实际节点上跑过,8 核机器上处理压缩日志时,-P 6左右能基本跑满 CPU,但内存会飙升;遇到内存密集任务,我一般保守地用-P 2。拿不准的时候,先跑一次小样,在 htop 里看资源水位,这对生产环境是一种负责。

另一个分寸是切分粒度。分片太小,进程调度开销占比高,总时间反而不一定短;分片太大,并行度打折扣。日志类场景我常用的粒度是 50M 到 100M,处理速度、内存占用都比较均衡。

4. 实操二:大 CSV 与数据导出文件的处理

4.1 一句话把 CSV 按行切分成多份

CSV 这类数据文件,最稳妥的切分维度一定是行。按字节切会把一行数据断成两截,后续导入必出问题。直接一行命令:

split -l 200000 -d -a 3 --additional-suffix=.csv sales_export.csv part_

生成part_000.csv、part_001.csv,每个文件 20 万行数据。20 万行的选择有考究:这个量在 Excel 里打开不会太卡,在普通脚本里处理也就几十秒,文件大小通常控制在几十 MB,后续上传下载都很轻松。如果你需要喂给 Excel,200000 行在打开速度和行列限制之间是个不错的折中。

4.2 保留表头的正确姿势

很多人第一次切 CSV 都会疑惑:为什么切出来的文件没有表头?split 本身只是个切刀,它不关心 CSV 结构,更不会自动复制表头。正确做法是先手动提取表头,再切数据体,最后把表头拼回每个分片。

head -1 sales_export.csv > header.csv tail -n +2 sales_export.csv > body.tmp split -l 200000 -d -a 3 --additional-suffix=.csv body.tmp part_ for f in part_*.csv; do cat header.csv "$f" > "$f.tmp" && mv "$f.tmp" "$f" done rm body.tmp

简单解释一下这几行的作用:head -1拿到表头;tail -n +2跳过第一行,把数据体导到临时文件;split 切数据体;循环把表头和每个分片拼接。这个流程在 100GB 级 CSV 上也适用,因为真正读进内存的只有单个分片。

如果你用的是 GNU split,还有一个纯--filter的写法,能省掉临时文件:

head -1 sales_export.csv > header.csv tail -n +2 sales_export.csv | split -l 200000 -d -a 3 --filter='cat header.csv - > $FILE.csv' - part_

注意这里的-代表标准输入,cat 会先把表头接上,再拼接分片数据。两种写法都能跑通,纯 shell 的 for 循环写法更适合在多个操作系统上通用,--filter写法更简洁但要求 GNU 环境。

4.3 自定义分隔符与多行记录

CSV 也不是只有一种形态。有的业务系统导出时用;或\t做列分隔符,有的系统把多行文本塞在一个字段里,导致一行记录里出现换行。默认的-l是按\n计数的,遇到字段内换行就会把一条记录拆成两半。

GNU split 提供了-t选项,用来自定义记录分隔符:

split -t ';' -l 10000 data.csv part_

这条命令以分号作为记录分隔符,每 10000 个分号分隔的记录切一个文件。注意,-t在 GNU 版本里要求单字符分隔符,如果你想分隔符是 Tab,要写成$'\t';如果你想以空字符分隔,写成$'\0'也是可行的。这个选项在处理一些老系统导出的边界标记文件时比较实用,但我不建议在普通 CSV 上滥用,因为 CSV 本身的分隔符解析远比单字符复杂,该用专用解析器时别硬用 split。

对于确需按复杂规则识别记录边界的文件,与其跟 split 较劲,不如用 awk。awk 可以按字段值做切分,后面的 5.2 我会演示一个例子。

4.4 文件合并与完整性校验

切完的数据最终还是要合并或校验。合并非常简单:

cat part_*.csv > result.csv

如果你用了文件名前缀part_,shell 通配符会按字典序展开。字母后缀aa、ab、ac顺序正确;数字后缀配合-a 3也会按000、001、002正确排序。这里必须提醒一个隐蔽问题:如果分片超过 10 个,而你没有用-a补齐数字位数,那么split -d生成的是0、1、2...9、10,按字典序排列会变成0、1、10、2、3...,cat 合并时顺序会错。解决办法是至少-a 2,分片超过 100 时用-a 3。

合并后校验完整性,我习惯用哈希对比:

sha256sum sales_export.csv > before.sha256 cat part_*.csv | sha256sum

如果两个 SHA256 一致,说明切分和合并没有破坏数据。对大文件来说,计算原始文件的哈希本身也要花时间,但相比数据错乱带来的排查成本,这点时间绝对值得。你可以把校验步骤写进脚本里,每次合并且动都自动执行。

5. 需要按内容切分时,改用 csplit、awk 与 dd

5.1 csplit:按匹配行切分文件

split 只认固定的大小或行数,无法“遇到某个标题就切一刀”。如果需求是按内容标记切分,csplit 才是正确的工具。csplit 的用法是给定一个匹配模式,它把文件从匹配位置切开。

举个例子,有一本 Markdown 文档,每个章节以#开头,想按章节拆成多个文件:

csplit -z -f chapter_ -b '%02d.md' book.md '/^# /' '{*}'

逐个解释选项:-z表示删除空文件;-f chapter_指定前缀;-b '%02d.md'指定输出文件编号格式,和后缀;最后的/^# /是正则匹配模式,{*}表示重复匹配直到文件末尾。跑完之后你会得到chapter_00.md、chapter_01.md等文件,每个对应一个章节。

csplit 还支持在匹配行前后偏移,比如你想把匹配行之后 3 行也包含进上一个文件,可以写/^chapter/ +3。不过这种偏移规则容易把自己绕晕,我建议能复用 split 的就别用 csplit,只有“按固定文本边界切分”这种明确需求才上 csplit。

5.2 awk:按字段维度拆数据

有时候切分规则跟行数、大小、标题都没关系,而是按数据本身的值来分。比如销售明细 CSV 里有region列,你想每个地区单独一个文件,用 awk 是最直接的:

awk -F, 'NR==1{header=$0; next} !($1 in seen){seen[$1]=1; print header > "region_" $1 ".csv"} {print > ("region_" $1 ".csv")}' sales.csv

为了不把表头给到每个文件前就看不懂,稍微解释一下:第一条记录把表头存进变量;第一次遇到某个地区值时,先写表头;之后所有该地区的数据行追加写入对应文件。实际使用中我更推荐写成脚本文件而不是一长串一行命令,因为调字段编号、处理多列拼接都会频繁改,写成多行更容易维护。

awk 的切分优势是灵活,劣势是当文件非常大时,打开的文件句柄数量可能很多。如果地区数量有限(几十个以内),没问题;如果按订单号切分,几百万个文件,awk 和 split 都不合适,应该走真正的分布式处理框架。

5.3 dd:精确到偏移的二进制切分

split 也能切二进制,但如果你需要“从第 N 个字节开始取 M 个字节”,dd 更合适:

dd if=data.bin of=part1.bin bs=1M count=100 dd if=data.bin of=part2.bin bs=1M count=100 skip=100

第一条命令取前 100 个 1M 块;第二条跳过 100 块再取 100 块,得到紧接其后的部分。dd 的count和skip都是块数,bs是块大小。它适合你已经知道精确偏移的场景,比如需要跳过固定大小的文件头,或者从损坏文件的某个 offset 恢复数据。

不过,日常切分我还是首选 split。原因很简单:split 会自动算出文件总大小决定切几刀,dd 要自己算count和skip,容易算错,而且写起来繁琐。dd 适合的是对偏移有明确要求的“外科手术式”操作,split 适合的是“平均分块”。

6. 实战排障:绕开这几个坑,脚本就成功了一半

6.1 常见报错速查表

我整理了实际使用中最常碰到的几类问题,直接给结论:

现象原因解决方式
报错output file suffixes exhausted默认后缀或指定后缀长度不够,分片数超过组合上限加大-a数值,比如-a 4;或改用-d数字后缀
生成的.csv文件没有表头split 不会识别 CSV 表头手动提取表头再拼接,参考 4.2 节
分片文件顺序在合并时错乱数字后缀位数不够导致字典序错乱用-a 3或更大位数补齐
macOS 上命令找不到选项BSD split 缺少 GNU 扩展选项用 GNU coreutils 的gsplit,或改用基础选项
--filter写出的文件只有一个.gz$FILE被 shell 提前展开为空用单引号包裹 filter 命令
wc -l统计的行数和预期不符原文件最后一行没有换行符用tail -c 1检查文件尾部,再决定要不要补换行
-t只能识别单字符分隔符GNU split 的限制换用 awk 或专用解析工具

这些坑我基本都踩过,最典型的是后缀耗尽和顺序错乱。前者通常发生在分片数量超过 676(26 个字母的平方)时;后者更隐蔽,常见于没加-a位数、分片超过 10 个的场景。在生产脚本里,这两个问题都会直接导致下游任务失败,所以我现在写任何 split 命令,几乎都会默认带-d -a 3。

6.2 跨平台差异与 GNU 扩展

如果你的脚本只在 Linux 上跑,前文所有选项都可以放心用。但如果你和我一样,笔记本是 macOS、服务器是 Linux,就得注意差异。

macOS 自带的 BSD split 支持的是 POSIX 基础能力:-a、-b、-l、-p这些基本项都有,但它没有-d、-n、--filter、--additional-suffix这些 GNU 扩展。这意味着同一个脚本在服务器上跑得好好的,拿到本地一执行就报invalid option。

两个解决办法。第一个是脚本里做兼容分支,根据uname判断平台,再选择参数;第二个更省心,在 macOS 上安装 GNU coreutils,用gsplit替代split。如果你在团队里维护公共脚本,统一约定“生产环境脚本只依赖 GNU coreutils”是最省事的做法,否则排查起来会很痛苦。

还有一个兼容性细节:前缀里如果包含目录路径,比如parts/xxx_,要确保parts目录已存在,split 不会帮你自动建目录。这个和平台无关,但很多人容易漏。

6.3 数据完整性核验与复盘

最后分享一个我的核验流程,每次切完大文件都习惯性跑一遍:

# 记录原始文件大小与行数 stat -c '%s' bigfile wc -l bigfile # 切分后统计所有分片的字节数和行数 cat part_* | wc -c cat part_* | wc -l

字节数、行数都能对上,说明切分没有丢数据。如果对不上,优先怀疑文件尾部没有换行符,其次怀疑--filter处理过程中有数据落地后被误删。这个核验成本很低,但能避免很多后续麻烦。

我自己在实际项目里,会把切分、压缩、哈希记录三步都写进同一个脚本,输出一份类似manifest.txt的分片索引。这样到了下游,别人拿到文件时不需要猜,看索引就知道每个分片对应的原始位置和校验值。这套习惯最初只是为了避免一次数据重导的惨痛教训,后来成为了团队的标准操作。

回到开头的问题:大文件切分这件事本身不难,难的是把规则选对、把边界处理好、把脚本跑到多个环境上都不出错。split 命令最让我欣赏的地方,就是它把复杂的数据切分抽象成了几个清晰的维度,你只需要想清楚你要按什么切,剩下的交给它。平时多积累几个顺手的参数组合,真到几十 GB 文件摆在面前时,你会感谢当时的自己。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询