1. 这活儿为什么值得专门研究我最早接触终端里把Excel转成CSV这个需求是在一台没有图形界面的Linux服务器上。当时有个业务系统每天要导出一份Excel报表然后由下游的数据管道用CSV格式去消费。服务器上没装Office我又不能为了一个转换功能就装一套LibreOffice的图形版上去更不可能每天手动把文件下载下来、用WPS另存为一次。那时候我就在想总得有个能在纯命令行环境里稳定干这活儿的方案吧。后来发现这个需求其实特别普遍。数据工程师要把Excel喂给ETL管道运维人员要做配置表格式转换测试人员要从测试数据Excel里生成批量导入脚本甚至有人把Excel转成CSV后拿到Neo4j里导入图数据库或者把CSV交给MATLAB做FFT分析。终端里处理Excel转CSV已经成了数据流转链路里特别基础、特别常被问到的一个环节。这篇文章我打算从决策思路、工具选型、实战命令到踩坑记录完整梳理一遍终端实现Excel转CSV的主流方案。无论你是运维、后端、数据分析师还是偶尔要在命令行里处理表格的普通开发都能从里面找到适合自己场景的做法。2. 方案选型的底层逻辑2.1 为什么不能把xlsx当普通文本直接处理很多第一次接触这个需求的人会问Excel文件不就是个表格吗终端里用cat、awk、sed能不能直接处理答案是明确不能。xlsx格式本质上是一个zip压缩包里面装着多个XML文件分别描述工作簿结构、工作表数据、样式、公式、共享字符串等。如果你用文本方式直接去读xlsx看到的会是一堆乱码——那其实是压缩后的二进制内容。这也就决定了终端里要完成Excel转CSV必须借助一个能解包并解析xlsx的工具。理解这个底层原理很重要因为不同的工具解析XML的方式不同直接决定了转换结果的细节差异比如公式是取计算结果还是取公式表达式、日期是以序列号输出还是格式化成可读文本、空单元格是跳过还是输出连续逗号等等。2.2 五个主流技术路线的对比从我这些年用下来的经验看终端里做Excel转CSV基本可以分成五条路线每一条都有自己最合适的场景。第一是专用命令行工具典型代表是SSV、in2csvCSVKit套件的一部分和xlsx2csv。这类工具干的就是这一件事参数通常很直观适合在脚本里快速调用。第二是脚本语言方案最常用的是Python的pandas或openpyxlNode.js则可以用SheetJS。这个方案的优势在于灵活转换之外还能顺手做数据清洗、列重命名、类型转换适合深度定制。第三是LibreOffice的无头模式soffice --headless --convert-to csv。它最大的价值是能处理老版.xls文件以及复杂的格式丢给Office套件自带引擎去兜底。第四是Windows环境下利用PowerShell调用Excel COM对象适合你本来就在Windows终端里、并且机器上装了Office的场景。第五是土办法如果是.xls老格式可以用文本编辑器打开另存为如果是xlsx先解压再处理XML。这个方法一般不推荐但当你手头什么工具都没装、又断网装不了包的时候它能救命。2.3 怎么判断该选哪个方案我的建议是按三条标准筛选运行环境、文件规模、使用频率。如果你在Linux服务器上优先考虑专用命令行工具因为它们的依赖最少。xlsx2csv是纯Python写的安装后连pandas都不用SSV是编译好的二进制一条命令就能跑。如果文件特别大比如上万行的数据我的经验是SSV的性能明显好于xlsx2csv它底层用Rust写的解析速度能快几倍到十几倍。如果你要频繁做转换并且需要转换后马上进行数据加工用Python脚本更合适你可以把转换和后续的字段处理合并成一个流程。如果只是为了偶尔转一次用in2csv最省心它参数全、文档多、网上能搜到的例子也最多。如果文件是老版.xls优先考虑LibreOffice或者Python的xlrd库搭配pandas。如果你是在Windows服务器上做批处理而且机器已经装了Office那么PowerShell COM方案是效率最高的不需要额外装任何东西。3. 专用命令行工具的实战要点3.1 SSV最省心的跨平台选择先说我最喜欢的SSV。它是一个Rust写的开源工具全称是Simple Sheet Viewer或者你直接理解成Spreadsheet conversion tool就行。它支持的格式很全xlsx、xls、ods、csv都可以互相转。安装很简单如果你的环境里有cargocargo install ssv如果你用macOS的Homebrewbrew install ssvLinux下也可以直接下载预编译的release包放到/usr/local/bin下面。最常用的转换命令ssv convert input.xlsx -o output.csv如果你想转指定的工作表Sheet用--sheet参数ssv convert input.xlsx --sheet 2024年数据 -o output.csv如果你想控制输出的CSV格式比如指定分隔符ssv convert input.xlsx -o output.csv --delimiter ;这个工具让我最满意的一点是它默认把公式的结果值输出而不是输出公式本身。这一点在数据流转场景里特别重要下游看到的是数据而不是Excel公式。还有一个细节它处理日期时默认按ISO格式输出不会出现那种一串数字看不懂的情况。3.2 in2csv适合需要快速查看和转换的场景in2csv是CSVKit工具集里的一个子命令。CSVKit可以说是一套完整的CSV瑞士军刀除了转换还有csvcut、csvgrep、csvgrep等配套命令用来切割、过滤、清洗CSV数据。如果你平时不止要转换还要对CSV做各种处理建议整套装上。pip install csvkit基本转换方式in2csv input.xlsx output.csvin2csv默认转换第一个工作表指定工作表用--sheet参数in2csv input.xlsx --sheet Sheet2 output.csv还有一个很实用的能力它能把JSON也转成CSVin2csv data.json data.csv这在API接口返回数据后需要落到表格的场景里特别好用。实测下来in2csv对常见Excel格式的解析比较稳定公式处理也OK但它依赖Python环境在完全没有Python的机器上需要先装Python。如果你本来就是Python生态的用户这个基本上零成本。3.3 xlsx2csv纯Python轻量方案xlsx2csv可以理解成一个专注只做xlsx转csv的Python脚本它跟in2csv的区别是in2csv是全家桶xlsx2csv是单点工具。装法也简单pip install xlsx2csv命令行用法xlsx2csv input.xlsx output.csv如果你想输出所有工作表并把它们各自存成文件xlsx2csv input.xlsx --all --outputdir ./csv_outputxlsx2csv还支持从stdin读入文件内容再处理这方便你在管道里使用cat input.xlsx | xlsx2csv - output.csv不过需要注意xlsx2csv对老版.xls格式支持不好遇到.xls文件还是得靠LibreOffice或者pandas。另外它的性能处理大文件时偏慢10万行以上的数据我实测过耗时明显比SSV高。3.4 LibreOffice无头模式老格式和复杂格式的兜底方案如果遇到.xls老格式或者Excel里带着复杂的格式、宏、图片SSV和xlsx2csv可能解析不对甚至直接报错。这时候用LibreOffice的无头模式是最稳妥的。soffice --headless --convert-to csv input.xls --outdir /tmp/csv_output这个命令的意思是以无界面模式启动LibreOffice把input.xls转成CSV输出到/tmp/csv_output目录。注意这里不能通过-o指定输出文件名只能通过--outdir指定输出目录文件名默认跟源文件同名只是扩展名变成.csv。如果你要转换xlsx到csv同样可以用soffice --headless --convert-to csv input.xlsx --outdir /tmp/csv_output它的优点是格式兼容性是最好的缺点也很明显启动LibreOffice实例比较慢单个文件转换可能要一两秒甚至更久转换大批量文件时性能是个瓶颈。另外它默认只转第一个工作表如果要转指定的工作表需要写一点额外的配置。还有一个容易踩的坑LibreOffice转换大批量文件时不当并发执行多个实例会锁死建议串行处理。3.5 工具对比速查表工具安装方式xlsx支持xls支持指定Sheet性能适用场景SSVcargo/brew/二进制好一般支持快跨平台、量大、无依赖要求in2csvpip install csvkit好一般支持中Python生态、需要后续清洗xlsx2csvpip install xlsx2csv好不支持支持中慢轻量、单文件快速转换LibreOffice系统包管理好好需配置慢老格式、复杂格式兜底4. 脚本语言方案灵活定制的进阶路线4.1 Python pandas最常用的数据处理组合当你需要转换后还能顺手处理数据Python加pandas是绕不开的组合。转换代码相当简单import pandas as pd df pd.read_excel(input.xlsx, sheet_nameSheet1) df.to_csv(output.csv, indexFalse, encodingutf-8-sig)这里有几个点值得多说几句。第一encoding”utf-8-sig”非常关键加了BOM的UTF-8 CSV文件在Windows上用Excel打开时不会乱码。如果你生成的CSV要交给Windows用户这个细节能帮你避免一个很大的沟通成本。第二sheet_name既可以传工作表序号从0开始也可以传工作表名称。批量转换所有工作表import pandas as pd xls pd.ExcelFile(input.xlsx) for sheet in xls.sheet_names: df pd.read_excel(xls, sheet_namesheet) df.to_csv(f{sheet}.csv, indexFalse, encodingutf-8-sig)第三pandas读取Excel时底层依赖openpyxl或xlrd。如果你只安装了pandas但没装openpyxl读取xlsx会报错需要先执行pip install openpyxl。处理xlsx首选openpyxl处理老版xls需要xlrd而且xlrd的版本必须小于2.0这个细节我在第7节再展开。4.2 Python openpyxl不装pandas也能干活有时候目标机器上只有一个很精简的Python环境装pandas可能带来一堆依赖问题。这时候直接用openpyxl就够了from openpyxl import load_workbook import csv wb load_workbook(input.xlsx) ws wb[Sheet1] with open(output.csv, w, newline, encodingutf-8-sig) as f: writer csv.writer(f) for row in ws.iter_rows(values_onlyTrue): writer.writerow(row)这个脚本的优点是依赖少只需要openpyxl一个库。openpyxl的iter_rows(values_onlyTrue)会把每行数据作为一个元组返回不包含单元格对象的额外信息性能比直接遍历cell对象好不少。但要注意一点openpyxl的load_workbook默认data_onlyFalse也就是它读出来的是公式表达式而不是计算结果。如果你需要的是计算结果加载的时候要指定data_onlyTruewb load_workbook(input.xlsx, data_onlyTrue)不过data_onlyTrue读出来的结果是缓存的计算值如果这个Excel文件从来没被Excel或WPS打开过、公式没有计算过那么这个缓存可能不存在读出来会是None。这是openpyxl一个比较隐晦的坑具体原因说过是Excel保存文件时公式的计算结果作为缓存一起存进去但如果文件是某些工具生成的可能只存了公式没存结果。4.3 Node.js SheetJS前端同学的顺手方案如果你是个前端或者Node服务端开发者不想引入Python可以用SheetJS社区版来处理。const XLSX require(xlsx); const workbook XLSX.readFile(input.xlsx); const sheet workbook.Sheets[workbook.SheetNames[0]]; const csv XLSX.utils.sheet_to_csv(sheet); require(fs).writeFileSync(output.csv, csv, utf8);这段代码会读取第一个工作表通过sheet_to_csv生成CSV字符串然后写入文件。如果你要转多个工作表遍历workbook.SheetNames就行。SheetJS社区版的局限在于个别复杂格式和样式可能会丢失但对纯数据转换来说完全够用。而且它的API风格比较统一浏览器端和Node端都能跑如果你要做一个网页端的Excel上传转CSV功能这套代码基本可以直接复用。4.4 封装一个批量转换脚本实际工作中经常遇到一个目录下几十个Excel文件要批量转换的情况。我一般会写一个简单的Python脚本用Path.glob遍历文件from pathlib import Path import pandas as pd input_dir Path(excel_files) output_dir Path(csv_files) output_dir.mkdir(exist_okTrue) for excel_file in input_dir.glob(*.xlsx): df pd.read_excel(excel_file) output_file output_dir / f{excel_file.stem}.csv df.to_csv(output_file, indexFalse, encodingutf-8-sig) print(fconverted: {excel_file.name} - {output_file.name})如果你担心格式兼容想用SSV来做批量转换在shell里一个for循环就解决了for f in excel_files/*.xlsx; do ssv convert $f -o csv_files/$(basename ${f%.xlsx}).csv done注意给路径加引号避免文件名带空格时分词错误。这个细节虽然基础但真有人踩过文件名里面带空格在for循环里容易变成两个参数。5. Windows终端与PowerShell方案5.1 利用本地Excel做COM转换如果你就在Windows终端里工作而且这台机器已经装了Microsoft Office那么用PowerShell调用Excel COM对象是最省事的方式几行代码就能完成转换$excel New-Object -ComObject Excel.Application $excel.Visible $false $workbook $excel.Workbooks.Open(C:\data\input.xlsx) $workbook.SaveAs(C:\data\output.csv, 6) $workbook.Close() $excel.Quit()这里SaveAs的第二个参数6代表CSV格式。注意CSV格式还分几种6对应的是CSV UTF-8Excel 2016之后版本的默认如果你的下游系统对编码有要求可以改用xlCSV值是6对应的是Macintosh CSV这里我更正一下Excel的XlFileFormat枚举中xlCSV 6是指MS-DOS CSV格式xlCSVUTF8 62是UTF-8 CSV格式。实际操作中如果是给Unix/Linux系统消费更推荐用62xlCSVUTF8$workbook.SaveAs(C:\data\output.csv, 62)5.2 指定Sheet转换的PowerShell写法如果你要转的不是第一个工作表直接SaveAs整个工作簿会把所有Sheet拼到一个CSV里。解决办法是先选中目标工作表$worksheet $workbook.Worksheets.Item(Sheet2) $worksheet.Select() $workbook.SaveAs(C:\data\output.csv, 62)5.3 无Office但有WPS怎么办如果你用的是WPS而不是Microsoft OfficeCOM对象名称可能不同。WPS安装后通常会注册ket.Application这个ProgID。你可以先试试创建一个通用对象不行就改用LibreOffice方案或者直接下载SSV的Windows二进制。还有一个细节PowerShell方案要求机器上装了Office而且首次运行时会弹出Excel的许可协议界面如果你的脚本是计划任务里跑的记得在代码里加上$excel.DisplayAlerts $false避免弹窗卡住任务。5.4 Windows环境下的其他补充工具Windows下还有一个微软官方出的工具叫Microsoft Access Database Engine安装后可以通过Jet/ACE OLE DB来读取Excel。这个方法我试过能用但没必要因为它安装包比较重而且写起来代码不少。日常还是优先PowerShell COM或者SSV。6. CSV编码与格式陷阱90%的人在这里翻车6.1 UTF-8和GBK的相爱相杀CSV是纯文本文件没有强制编码标准这就导致一个经典问题Linux下生成的UTF-8编码CSV在Windows上用Excel双击打开时中文全部乱码。解决这个问题最简单的方法就是在生成时加上BOM也就是文件开头加三个字节EF BB BF。用pandas的话df.to_csv(output.csv, indexFalse, encodingutf-8-sig)如果你用SSV工具没有这种情况吗实际上SSV默认输出是UTF-8无BOM。如果碰到Windows接收端乱码可以转换后用sed补一下或者用iconv加BOMprintf \xEF\xBB\xBF output_with_bom.csv cat output.csv output_with_bom.csv如果你处理的Excel本身是中文环境创建的文件里的字符串可能是GBK/GB2312编码但xlsx内部存储统一是UTF-8所以从xlsx转出来的CSV默认UTF-8问题不大。倒是如果你要去处理一个已经是CSV格式但编码是GBK的文件要转换编码的话用iconviconv -f GBK -t UTF-8 input_gbk.csv output_utf8.csv6.2 换行符问题CRLF和LF的区别Excel生成的CSV换行符通常是CRLF也就是\r\n。而Linux工具链里的很多命令期望LF也就是\n。如果你发现CSV文件里每行末尾有个^M那就是CRLF带来的显示问题。用sed清理sed -i s/\r$// output.csv或者用dos2unix工具一行命令搞定dos2unix output.csv需要注意的是如果单元格内容里本身包含回车换行直接全局替换\r会破坏数据。Excel中一个单元格内用AltEnter换行在CSV里表现为这个单元格的内容被双引号包裹内部包含真实的LF。这种情况要处理的话必须解析引号状态再决定哪些换行符该保留。6.3 逗号、引号和公式CSV转义规则必须懂CSV格式不是用逗号分隔就行这么简单。当单元格内容里包含逗号、双引号或换行时必须用双引号包裹整个字段字段内部的引号用两个双引号转义。比如一个单元格内容是他说你好后面带个逗号生成的CSV字段就是他说你好后面带个逗号。这个规则大多数转换工具会自动处理但如果你自己用文本处理命令去做转换很容易忽略。之前见过有人用awk把Excel导出的文本格式文件强行按逗号切分结果数据全错位了。还有公式的问题。很多Excel文件里的单元格是公式比如D2*E2。转换为CSV时如果工具把公式表达式输出下游拿到的就是一堆以等号开头的文本完全不是数值结果。前文提到SSV、in2csv这类工具算法设计时会优先取公式缓存的计算结果但如果你用openpyxl且没开data_onlyTrue输出的就是公式本身。这是个特别容易被忽略的差异点。6.4 长数字变成科学计数法Excel在界面里显示一长串数字时超过一定位数会变成科学计数法比如身份证号、银行卡号显示成1.23457E17。转换CSV时这个问题也可能被带出来。解决办法是在转换前先用Excel或者openpyxl把目标列的格式设置为文本。在pandas里读取时可以指定dtypedf pd.read_excel(input.xlsx, dtype{身份证号: str})实测这种场景在银行、政务数据交换中很常见提取出来的字段如果变成科学计数法下游导入数据库会丢精度损失很大。7. 批量转换与自动化实战7.1 写一个健壮的批量转换脚本把前文的脚本稍微升级一下加入日志、错误处理、文件存在性检查#!/bin/bash # 批量转换excel到csv # 用法: ./xlsx2csv_batch.sh /path/to/excel_dir /path/to/output_dir INPUT_DIR$1 OUTPUT_DIR$2 mkdir -p $OUTPUT_DIR for file in $INPUT_DIR/*.xlsx $INPUT_DIR/*.xls; do if [ -f $file ]; then base$(basename $file) name${base%.*} echo 转换中: $file ssv convert $file -o $OUTPUT_DIR/$name.csv if [ $? -eq 0 ]; then echo 成功: $name.csv else echo 失败: $file fi fi done echo 批量转换完成这个脚本简单实用关键词判断$?是否等于0来检查上一条命令是否成功。注意判断文件存在用的是-f避免数据源目录里没有匹配文件时glob模式直接被当作字面量传给ssv。7.2 定时任务每天自动转换把这个脚本挂到crontab里每天凌晨自动执行0 2 * * * /opt/scripts/xlsx2csv_batch.sh /data/excel /data/csv /var/log/xlsx2csv.log 21这里把错误输出重定向到同一个日志文件。做定时任务时有个关键点脚本里所有命令要用绝对路径因为crontab环境下的PATH通常不包含/usr/local/bin直接写命令名可能找不到。7.3 MD5校验确保CSV生成正确转换完的文件需要校验完整性时用md5sum生成校验值md5sum output.csv output.csv.md5之后校验md5sum -c output.csv.md5这个用法在数据对接场景很常用以防文件在传输或者转换过程中被改坏。如果你要校验的是源Excel和生成的CSV是否对应可以转换前记录源文件的md5转换后记录CSV的md5在交付文档里一并给出。8. 常见问题排查与避坑实录8.1 问题速查表现象可能原因解决办法生成的CSV在Windows Excel打开乱码编码是UTF-8无BOM转换时用utf-8-sig或手动加BOM每行末尾有^M换行符是CRLFdos2unix或sed清理长数字变科学计数法列被当成了数字类型转换时指定列类型为文本输出的是公式不是值工具读取了公式表达式openpyxl加data_onlyTrue或换SSV转换.xls报错工具不支持老格式用LibreOffice或pandasxlrd(版本2.0)工作表不对工具默认只取第一个Sheet显式指定--sheet或sheet_name大文件转换极慢工具解析性能瓶颈换SSV或者减少不必要的工作表PowerShell COM弹窗卡住有提示对话框设置DisplayAlerts$false文件名带空格导致转换失败脚本没加引号路径加双引号8.2 我踩过的一个坑xlrd新版读不了xlsx这个坑我很想单独说一下。有一次在客户环境部署转换脚本环境里pandas版本很新我直接pip install xlrd想把老版.xls文件也支持了结果脚本直接报错xlrd.biffh.XLRDError: Excel xlsx file; not supported。原因是xlrd 2.0之后只支持.xls不支持.xlsx了而pandas读取.xlsx实际依赖的是openpyxl。解决办法是如果确实需要读老.xls装xlrd1.2.0这个版本两者都支持如果只需要处理.xlsx安装openpyxl即可别装xlrd。8.3 另一个坑LibreOffice转换时序问题LibreOffice的soffice命令第一次运行时会在用户目录下初始化配置文件如果脚本在容器或者CI环境里第一次跑这个过程会比较慢。我一度以为是卡死了其实是它在后台初始化。解决方案有两条一是提前手动跑一次soffice --version把初始化的时间消耗在部署阶段二是在命令里加上-env:UserInstallation参数指定一个可写的临时目录soffice --headless -env:UserInstallationfile:///tmp/lo_profile --convert-to csv input.xlsx --outdir /tmp/csv_output这个参数在容器环境下特别重要因为容器里没有家目录或者家目录只读时不指定这个会直接报错。8.4 关于终端复用这个热词的联想说到终端顺便提一句如果你经常要在命令行里来回切换目录、反复试验各种转换命令善用终端复用工具比如tmux、screen能省很多事。现在比较流行的Tabby终端工具也是不错的选择它的SSH管理、标签页、分割 pane 功能比系统自带的终端体验好不少。我自己日常就是在Tabby里开一个窗口左边跑转换命令右边用vim预览生成的CSV效率比来回切窗口高得多。这不是转换本身的技术问题但确实能改善终端使用体验。9. 最后再分享一个小技巧如果你要转换的目标CSV是需要导入数据库的建议转换后顺手跑一遍数据校验。一个简单的做法是转换前后分别统计行数和关键列的非空值数量对比一下是否一致。用wc -l统计CSV行数时注意如果单元格内容里含有换行符行数会比实际数据行数多这种情况要基于第一列或者某个自增字段去数才是准确的。另外我自己用下来最顺手的组合是能用SSV就用SSV它的性能和格式细节处理都最省心需要数据加工时用pandas遇到老格式用LibreOffice兜底。这三个方案覆盖了我遇到过的99%的Excel转CSV场景。你可能不需要全都掌握但至少要清楚自己手上有什么工具可选真到生产环境出了问题不至于手足无措。