首页 / 资讯中心 / 文章详情

大文件多列数据去首列实操:awk、cut、sed与Python选型指南

大文件多列数据去首列实操:awk、cut、sed与Python选型指南 ★ FEATURED ARTICLE
最近处理一批链路数据的时候又撞上一个看起来人畜无害的需求文件不小,几个 GB几十列列与列之间用制表符分隔第一列是早期预留错的字段现在下游模型不需要它要把“多列数据去除首列操作”做掉其他列原样保留。单看这个标题很多人的第一反应是“Excel 删一列不就行了”“sed 把第一个分隔符前后删掉不就完了”。真上了生产环境你会意识到大文件场景下的去首列并没有表面那么简单选错工具或漏看一个隐藏字符轻则白跑半小时重则把整列数据错位喂给下游排查一晚上。这篇文章就来聊清楚大文件、多列数据的首列删除到底该怎么操作。我会从动手前的文件摸底讲起把 awk、cut、sed 和 Python 流式处理这几种常用路线的边界条件、写法、验证方法全部拆开讲最后附上我自己的选型习惯和踩坑记录。适合需要批量处理 CSV/TSV 大文件的数据分析、后端开发、ETL 工程师以及被超大文本表折腾过的人参考。1. 为什么一个“删一列”的操作会变成正事1.1 需求本身不复杂复杂的是“列边界”去首列的本质不是“删除一行开头的那几个字符”而是“把每一行的第一个字段切掉再把剩余字段按原顺序重新拼成一行”。这句话听起来很简单但它引出了一个关键问题列边界到底由什么决定。制表符分隔的文件里第一列就是第一个\t之前的内容剩下的从第二个\t之后开始。这种规则很清晰但真实业务数据里总有意外某个字段内部藏着制表符某一行实际只有 40 列而表头写了 43 列有些行是空行文件最后一行没有换行符。这些都是“列边界”被扰乱的情况。如果只用“删掉行首到第一个分隔符”的文本替换逻辑去做遇到列数和格式不规整的行结果就是剩余字段集体错位而且这种错位往往很难一眼发现。我之前处理过一个 43 列的 TSV第一列是注册时间戳下游数据模型里已经有独立时间字段第一列属于冗余列。需求是去掉第一列后列数从 43 变成 42这样刚好能对齐下游表结构。听起来是个标准的cut -f2-用法但实际文件因为早期采集脚本的 bug中间有几百行混入了竖线字符导致这些行的列数偏多简单一刀切之后下游立刻报列数不匹配。所以第一步不是敲命令而是先把列边界规则彻底搞清。1.2 大文件场景的三块绊脚石第一块是内存。任何“一次性把整个文件读进内存再处理”的方案在 1GB 以上都会遇到明显瓶颈。普通配置的机器光读入一张上千万行的表格就得好几个 GB 内存更不用说中间还会有一堆临时对象。第二块是编辑器。Excel、WPS、甚至不少现代文本编辑器打开几百 MB 的文件就已经开始卡顿几个 GB 的文件基本是直接打不开或者预览时假死。即便强行打开要在几十列里选中第一列删除再保存操作效率也低到让人怀疑人生。第三块是列边界的判定。多列数据的“首列”到底依据什么界定必须用到文件本身的分隔符、引号规则、转义规则。如果分隔符是制表符第一列就是第一个\t之前如果是逗号就要小心引号内部可能含有逗号。很多人之所以在处理大文件时翻车就是没有意识到“列”不是直观的一行一段而是由分隔符和引号共同约束出来的逻辑结构。2. 动手之前快速摸底文件结构、编码和列数规整性2.1 用 file、head 和 cat -A 看清“看不见的字符”我拿到任何一个待处理的数据文件第一步永远是file不是急着删列。这条命令可以快速告诉我们文件的编码类型、换行风格和基本结构。file big.tsv # 常见输出: big.tsv: UTF-8 Unicode text, with very long lines (2000 chars), with CRLF line terminators看到CRLF line terminators就要立刻警觉这是 Windows 导出的文件后面处理时\r字符可能会混进最后一列。接着用head看结构再用cat -A把隐藏字符显示出来head -n 5 big.tsv | cat -A如果看到行内的分隔符显示为^I说明是制表符行尾显示为$说明是换行如果第一行开头出现M-BM-之类的乱码标记说明带 UTF-8 BOM。这些隐藏字符虽然肉眼不可见但在去首列操作里会直接影响结果尤其是 BOM它会让第一列的首个字符多出一个不可见前缀等输出后你又得处理一遍。2.2 用一条 awk 检查列数是否规整删列之前必须先确认文件各行列数是否一致。一条命令就能做到awk -F\t {print NF} big.tsv | sort | uniq -c | sort -nr | head这条命令的含义是以制表符为分隔符逐行读取打印每行的字段数 NF然后统计每个字段数出现的次数按次数降序展示。如果结果只有一行比如12000000 43说明所有行都是 43 列文件结构很规整后面可以直接用简单的命令行工具。如果看到多种列数比如 43 列出现 1190 万行、44 列出现 2000 行、40 列出现 1800 行就说明文件里有嵌套分隔符、缺字段或异常行。此时不能用cut -f2-一刀切必须先决定这些异常行怎么处理是丢弃、单独输出还是按某种规则补全。这一步的成本只有几秒却能避免处理后下游报错时才开始怀疑人生。2.3 体积、行数和格式决定工具选型做完前两步还要给文件做个“体积画像”。wc -l看总行数du -h看占用空间wc -l big.tsv du -h big.tsv个人经验是100MB 以下用 Python 的 csv 模块随便处理100MB 到几个 GBawk 和 cut 这类 C 实现的工具非常合适速度快、内存低如果文件里还带引号字段、字段内有换行那不管多大都得认真考虑 Python csv 这种真正理解 CSV 语义的方案而不是拿文本替换硬拼。选型可以简化为三问第一分隔符是否唯一且不藏进字段第二文件大小是否超过内存零头第三字段是否出现引号包裹。三个问题都通过命令行工具可以放心用有一个不通过就往脚本方案上靠。3. 首选方案awk、cut、sed 怎么选、怎么写才不破坏其他列3.1 awk 删除首列的安全写法规整 TSV 文件里awk 是我最常用的工具。因为它能设读入分隔符和输出分隔符还能在行处理时对字段做逻辑判断。最直观的写法是这样的awk BEGIN{FSOFS\t} {$1; sub(/^\t/, ); print} big.tsv cleaned.tsv这里FSOFS\t把输入的字段分隔符和输出的字段分隔符都设成制表符。$1的意思是把第一个字段置空然后再输出整行。但注意置空之后awk 在重新拼接输出时会保留一个前导制表符所以后面要跟一个sub(/^\t/, )把这个多余的前导制表符删掉。这样输出的才是“去掉第一列后的完整行”。如果文件里有空行或只有一列的行这个写法需要加个保护awk -F\t NF1{$1; sub(/^\t/, ); print} big.tsv cleaned.tsvNF1意味着只有行内字段数大于 1 时才执行删列逻辑否则原样输出。这样能避免把“只有一个字段的行”变成一个空行或产生意外内容。另一个更绕但完全不依赖 sub 的写法是awk -F\t {for(i2; iNF; i) printf %s%s, $i, (iNF ? ORS : OFS)} big.tsv cleaned.tsv这种方案从第二个字段开始循环输出字段间用 OFS行尾用 ORS。好处是逻辑更直白不会出现前导分隔符问题但代码稍微长一点。对于清洗脚本来说我更推荐前一种$1的写法好读、易维护。3.2 cut 最快但有边界限制cut 是专门干“按列切”的工具命令短、速度快、内存占用极低。规整 TSV 去首列可以一行搞定cut -f2- -d$\t big.tsv cleaned.tsv-f2-表示输出从第 2 列到最后一列-d$\t指定制表符为分隔符。这里$\t是 Bash 里的写法能正确转义出真正的制表符如果直接写-d\t很多旧版工具反而会把反斜杠和字母 t 当成两个字符。cut 的优点是性能我实测 1.2GB 规整 TSV 大概 20 秒左右跑完而且内存占用非常低。但它有几个硬边界。第一它不识别引号规则如果字段本身用引号包住且内部含有制表符cut 会把这个制表符当成真正的分隔符导致列错位。第二如果文件是 CRLF 行尾\r会残留在最后一列里建议先转行尾再处理。第三cut 对列数不一致的行没有任何感知它只是按位置切文本不会告诉你哪一行多了少了一列。所以 cut 最适用的场景是数据结构很规整、分隔符不会逃逸、字段不会被引号包裹。但凡有一点不确定我宁愿用 awk 或 Python。3.3 sed 的适用边界sed 也可以用来删首列写法是这个sed s/^[^\t]*\t// big.tsv cleaned.tsv它的逻辑是匹配行首开始、到第一个制表符前的所有非制表符字符连同这个制表符一起删掉。也就是把“第一列 一个分隔符”整体抹掉。适用于分隔符明确的 TSV 文件。sed 的优点是一行命令、语义直观但缺点也很明显。它本质上是纯文本替换根本不理解列的概念。遇到行首就是空字符串、空行、以及字段中本来就带有^或\t的情况时处理结果可能不符合预期。另外如果第一列本身就是空的也就是行首直接是制表符s/^[^\t]*\t//会匹配不到任何内容吗其实它会匹配“行首 零个非制表符 第一个制表符”所以也能删掉前导制表符但后续字段是否因此错位需要看具体数据。整体而言sed 适合你只是想快速处理干净文件、并且明确知道行首一定是“非制表符开头”的场景。一旦文件复杂起来我建议直接上 awk。3.4 三者的取舍对比表特性awkcutsed自定义分隔符支持FS/OFS 灵活支持-d 指定通过正则匹配区分引号包裹字段不支持不支持不支持空行输出默认保留保留保留对列数不一致的应对可用 NF 分支处理无感知纯文本删除单行命令可读性中等高较高内存占用极低极低极低最适用场景规整/半规整 TSV规整且超大的 TSV快速、一次性、结构简单要给排个序的话规整 TSV 里我首选 cut因为它最快、命令最不容易写错一旦需要处理空行、字段数不一、或者想加点逻辑就切成 awksed 基本只在我手边只有 POSIX 工具、又不想多装东西的时候才用。4. Python 流式处理用内存换不来的文件就交给逐行读写4.1 为什么别上来就 pandas.read_csv很多人看到“处理 CSV”第一反应就是pandas.read_csv。但大文件去首列这个场景我强烈建议不要一上来就上 pandas。原因很简单read_csv在读取一个 1.2GB 文件时会把整张表的所有值做类型推断建立索引额外维护一份 DataFrame 对象内存占用轻松飙到 5GB 以上。对很多开发机来说这就是直接卡死。即便你写chunksize100000分块读入仍然绕不开 pandas 在每一块里做类型推断和数据对齐的成本而且分块后还要处理列拼接问题代码复杂度上去了速度却不一定比纯文本流式处理快。去首列是一个非常纯粹的文本转换任务你不需要数据分析、不需要类型推断、不需要聚合。对这种任务一个 5 行的循环脚本往往比 pandas 快得多也更可预测。4.2 一个足够用的流式脚本如果文件是规整 TSV且没有引号包裹字段最轻量的写法是input_path big.tsv output_path cleaned.tsv with open(input_path, r, encodingutf-8, errorsreplace) as f, \ open(output_path, w, encodingutf-8, newline) as g: for line in f: if \t not in line: g.write(line) continue g.write(line.split(\t, 1)[1])这版只处理第一列前的部分找到第一个制表符直接写后面的内容。好处是它不会把整行拆成几百个字段的列表内存开销很小速度也快。split(\t, 1)这种带第二个参数的写法很关键它表示只分割一次剩下的字符串原封不动保留不会因为后续还有大量制表符而白白多做分割。如果文件是带引号的 CSV那就需要动真格的解析器直接用 Python 标准库的 csv 模块import csv with open(big.csv, r, newline, encodingutf-8-sig) as f, \ open(cleaned.csv, w, newline, encodingutf-8) as g: reader csv.reader(f) writer csv.writer(g) for row in reader: if row: writer.writerow(row[1:])utf-8-sig读入时会把 UTF-8 BOM 自动去掉这正好解决了第一节说的隐藏字符问题。csv 模块对引号字段、内嵌换行字段、逗号在引号内的场景都处理得正确。代价是它写出的结果会做规范化比如该加引号的地方会加引号这和原始文件的格式不完全一样。如果你只想保留原文件的原始字节风格那 csv 模块的规范化可能会让你不太满意但大多数下游消费场景都能接受。4.3 处理表头、空行和特殊字符的三个小细节第一个细节是表头。如果希望表头那一行也走同样的去首列逻辑上面的脚本天然就是这样的不需要改。如果希望表头原样保留就加一个首行判断first True for row in reader: if first: writer.writerow(row) # 保留表头原样 first False continue if row: writer.writerow(row[1:])第二个细节是空行。csv 模块读到一个空行时会给一个空列表[]直接跳过就行。如果用的是 split 版本要判断line本身是否只有换行符避免把空行写成一个奇怪的“空字符串加换行”。第三个细节是编码。不是所有文件都是 UTF-8。老业务系统导出的数据常见 GBK、GB18030、甚至 latin-1。读错编码会导致不可预知的乱码替换。建议先用工具检测再让脚本按检测出来的编码读入。但要注意如果文件混合了多种编码那不管用什么errors策略都只能做到“少崩”无法保证数据完全无损这种情况最好先在上游修导出逻辑。5. 验证、性能测试与三个真实踩坑点5.1 先验证再交付行数、列数、抽样对比去首列做完不是终点还要确认三件事总行数有没有变每一列的列数是不是都从 43 变成了 42第一列内容是不是真的没了。我的固定验证流程是wc -l big.tsv cleaned.tsv awk -F\t {print NF} cleaned.tsv | sort -nu | head head -n 3 big.tsv head -n 3 cleaned.tsvwc -l对比前后行数理论上原文件 1200 万行处理后也应该是 1200 万行。如果对不上多半是最后一行没有换行符之类的问题。接着看列数分布处理后如果之前是 43 列现在应该是 42 列。最后抽看前几行确认第一列确实没了剩余列顺序也没乱。抽样的范围也要注意。文件头部、中间、尾部可能由不同的采集逻辑生成。最好从头部、大约 1/3 处、大约 2/3 处、尾部各抽几行检查别只看表头后面那几行就宣布成功。5.2 我拿 1.2GB 文件做的对比结果之前有一次我拿一个 1.2GB、1200 万行、43 列的规整 TSV 做过一轮简单对比机器是普通四核 CPU磁盘为 SSD。结果大致是这样的方案耗时内存备注cut -f2-约 20 秒 100MB规整文件最快awk 删首列约 30 秒 100MB灵活可处理异常行python split 流式约 45 秒约 150MB可灵活处理编码python csv 流式约 70 秒约 150MB支持引号字段这个结果说明规整 TSV 用命令行工具的效率优势非常明显。除非你的文件里有引号字段、字段内含换行符否则没必要为了去一列而启动一个重量级脚本。反过来一旦文件格式复杂命令行工具再快也是错的该用 csv 模块就用。5.3 踩坑点一、二、三第一个坑分隔符藏在引号字段内部。我处理过一个以竖线|分隔的数据文件本以为是严格竖线分隔结果有一部分字段内容本身含竖线而且被引号包住。cut -d| -f2-直接把引号内的竖线也当作列分隔符输出列数大规模错位。最后改用 Python csv 模块把 delimiter 设为|、quotechar 设为才真正把列边界处理对。验证时不能只看前几行要看全量列数分布。第二个坑BOM 与\r。UTF-8 with BOM 的文件第一列内容前面藏了一个不可见 BOM。只关注首列时影响不大但如果后续还有别的列处理BOM 就会残留成脏数据。Windows 导出的 TSV 行尾是\r\n用cut -f2-去完列后拿脚本去处理最后一列会发现末尾多一个\r。所以遇到 CRLF 文件我一般先dos2unix或统一按 CRLF 处理。第三个坑文件末尾没有换行符。wc -l对不以换行结尾的行经常会漏数而 awk、sed 和 Python 处理最后一行时又往往会补一个换行输出。这个差异会让你的“前后行数对比”看起来对不上。曾经有次原文件wc -l是 1200 万处理后变成 1200 万零 1 行排查半天才发现是最后一行没有换行符被追加了\n。解决方案很简单写 Python 时统一保证每行以\n结尾并且心里清楚这种差异来自哪里。6. 最终选型速查和我的使用习惯6.1 一张表定位到你的场景文件情况推荐入口一句话注意严格 TSV无引号字段cut 或 awkcut 最快awk 可处理空行带引号字段的 CSVpython csv别用文本替换硬拼超大文件数 GB 且规整cut 加多文件分片先确认列数分布需要保留表头python split / csv首行单独判断不确定列数规整性awk 先看 NF 分布先诊断再处理文件是 CRLF 行尾先转行尾再处理处理完检查\r6.2 最后的习惯我现在的固定动作是这样的先跑file、head -n 5 | cat -A、awk NF 分布三步诊断选型之后用wc -l和抽样前几行做前后对比最后再拿一个下游消费脚本的小样例跑通。整套动作耗时不到五分钟但能挡掉大部分“看着没问题实际列错位”的事故。多列数据去除首列操作说到底不是背一条命令而是先把文件的列边界和格式看清再用对的办法把第二列到最后一列原样输出。每多花一分钟用在诊断和验证上就能少花一小时在数据错位的善后上。希望这篇整理能帮你少踩几个我踩过的坑。
阅读完成 · 觉得有帮助?
咨询建站