拓冰建站拓冰建站
首页 / 资讯中心 / 正文

Linux换行符CRLF与LF差异详解:dos2unix实用指南

凌晨一点上线窗口。脚本跑了一半就挂了报错信息只有一行-bash: ./deploy.sh: /bin/bash^M: bad interpreter: No such file or directory。乍一看以为是解释器路径写错了可#!/bin/bash明明没毛病。直到用cat -A把脚本打出来才发现每行结尾都挂着一个^M。交上来的deploy.sh是在Windows的记事本里编辑完直接传到服务器的换行符全部变成了CRLF。当时线上还有人等着手边又没装dos2unix只能sed一把梭。从那以后我接手任何Linux项目第一件事就是把dos2unix装上谁传上来的文件只要发现^M一分钟内解决。dos2unix是Linux下一个专门处理换行符格式的命令行工具。它做的事很简单把Windows/DOS格式文本文件里的CRLF换行符转成Unix/Linux标准的LF。功能单一的另一个说法是足够专注——它稳定、快速、覆盖面广是文本文件跨平台流转时最省心的一个工具。这篇文章我会把换行符差异的来龙去脉、实际工作中的高频踩坑场景、dos2unix的完整用法和批量处理技巧以及没有dos2unix时的替代方案一次讲清楚。适合刚接触Linux的新手也适合在运维和开发一线被文本格式问题折磨过的老手收藏备用。1. CRLF与LF换行符差异的来龙去脉1.1 回车与换行的历史要理解为什么会有换行符转换这回事得先回到电传打字机时代。CRCarriage Return回车和LFLine Feed换行是两种控制字符CR让打印头回到行首LF让纸张向上滚动一行。当时用两个字符组合CRLF来表示换行是为了给机械结构留出足够的动作时间本质上是硬件限制下的妥协方案。后来计算机系统各自为政对换行符的选择出现了分裂。Unix在诞生时就决定只用一个LF来表示换行简化了文本处理逻辑——读取和解析时少一个字符要处理很多C语言的字符串函数也会少很多边界问题。Windows则延续了DOS时代的习惯采用CRLF。而早期的Mac OS还用过单独的CR直到macOS转向BSD内核后也统一到了LF。这三套标准并存了几十年到了今天Windows和Linux/Unix之间的换行符差异依然是文件互换时最经典的坑。1.2 Linux下为什么只认LFLinux内核和绝大多数命令行工具在设计时默认文本行是以LF结尾的。shell按行读取脚本时用LF作为行分隔符C库的fgets、getline也按LF来切分。如果一行真正的结尾是\r\n读取时\r就会被当成行内容的一部分而不是行结束的标志。这就好比Excel里的单元格默认按逗号分隔你偏要把数据用分号黏进去结果所有字段都串到一格去了。一个字符的差异会让文本的语义完全变样。在跨平台协作越来越频繁的今天每个人手上都可能有Windows编辑过的脚本、配置文件、SQL脚本。只要文件一进入Linux环境换行符就成了隐藏的地雷。1.3 换行符混用会引爆什么问题换行符混用最常见的表现就是命令解释器、配置解析器和文本处理工具出现各种莫名其妙的行为。轻则一条命令报错重则整个服务起不来。而且这类问题有个特点直接看文件内容几乎看不出来必须用十六进制或专用工具才能发现尾部的0D 0A。更麻烦的是换行符问题往往是叠加在其他问题之上的。比如一个文件可能同时有CRLF换行符和GBK编码你只处理编码不处理换行符会觉得总有些诡异符号没清掉只处理换行符不处理编码中文字符又会继续乱码。所以排查这类问题时换行符和编码必须分开考虑dos2unix解决的是前者。2. 被\r坑过的真实场景2.1 bad interpreter脚本第一行直接爆雷最常见的翻车现场就是脚本报bad interpreter。我在开头写过那个报错#!/bin/bash^M被内核当成了解释器的完整路径。因为脚本文件的shebang行是以#!开头后面跟一个绝对路径内核会把这个路径拿去查找可执行文件。/bin/bash^M这个路径当然不存在所以马上报错。$ ./test.sh -bash: ./test.sh: /bin/bash^M: bad interpreter: No such file or directory排查思路很简单先看文件是不是CRLF。我习惯用cat -A它会把不可见字符显示出来行尾的^M一眼就能看见。也可以用file命令$ file test.sh test.sh: Bourne-Again shell script, ASCII text executable, with CRLF line terminators如果file输出里有with CRLF line terminators那就是换行符的锅。打开文件直接dos2unix test.sh再执行就正常了。2.2 配置文件解析失败DATABASE_URL后面多了一个\r这个案例比脚本报错更隐蔽。一次部署Go应用环境变量从.env文件读取配置里写了DATABASE_URLmysql://user:passhost/db看起来没有任何问题但应用启动时总是报数据库地址不合法。折腾了半天最后用十六进制看了一下文件尾部$ od -c .env ... D A T A B A S E _ U R L m y s q l : / / \r \n问题一目了然URL字符串末尾被塞进了一个\r。程序读到的实际上是mysql://user:passhost/db\r数据库驱动解析主机名时把\r当成了URL的一部分当然连接不上。MySQL、PostgreSQL、nginx、systemd的配置文件都可能遇到类似问题——解析器不认CRLF就会报语法错误或格式错误。遇到这类情况记得先把文件统一成LF。2.3 crontab任务不执行与SQL导入报错crontab文件在某些系统上对CRLF比较敏感。Windows上写完计划任务直接传到Linux服务器用crontab -e编辑时看着正常但cron守护进程加载时可能因为解析问题直接跳过任务日志里还不一定有明确提示。检查方法很简单用od -c或者dos2unix -i看一下文件里的CRLF数量不为零就先转再装。SQL脚本也一样。Windows上Navicat或记事本导出的SQL文件传到Linux上用mysql命令行导入可能报ERROR at line 1或者语法错误。行尾的\r在某些严格语法检查下会被当成非法字符。曾经有个同事处理一份200MB的SQL备份文件导入时报了几百条语法错误文件检查了半天才发现每行都是CRLF。处理完换行符之后一次导入成功问题全没了。2.4 代码评审中的^M与shell变量的隐形字符串用git diff查看改动时如果文件里混了CRLF显示的修改行末尾会挂着一个^M。代码评审工具里看还好但直接在终端里看就特别碍眼。而且如果不统一换行符团队里不同客户端来回改文件diff会越来越大最终把整个文件的每一行都标记成改动。还有个更隐蔽的场景shell脚本读取用户输入或文件内容后做字符串比较read value if [ $value yes ]; then echo matched fi如果脚本本身或者被读取的文件是CRLF格式$value尾部带着\r和yes永远比较不相等。这类问题靠肉眼几乎发现不了必须用cat -A或者xxd这类工具才能看见。3. dos2unix基础用法与参数拆解3.1 最基础用法原地转换dos2unix默认对指定文件进行原地转换直接把文件里的CRLF替换成LF$ dos2unix deploy.sh dos2unix: converting file deploy.sh to Unix format...一条命令完事。转换之后文件内容发生了改变但文件名不变。如果文件已经是Unix格式dos2unix不会有多余的动作直接返回所以重复执行是安全的。安装也很简单主流发行版包管理器都有# Debian/Ubuntu apt install dos2unix # CentOS/RHEL yum install dos2unix # macOS brew install dos2unix3.2 常用参数逐个说清楚dos2unix参数不多但每个都有明确的使用场景。我按频率从高到低列一下参数作用典型用法-o原地转换默认行为dos2unix -o file.txt-n转换并写为新文件dos2unix -n in.txt out.txt-k保留文件原始时间戳dos2unix -k file.txt-q安静模式不输出提示dos2unix -q file.txt-s跳过二进制文件dos2unix -s file-f强制转换dos2unix -f file-c指定转换模式dos2unix -c mac file.txt-i查看文件换行符信息不转换dos2unix -i file.txt-r保留BOMdos2unix -r file.txt--add-bom添加UTF-8 BOMdos2unix --add-bom file.txt-n适合需要保留原始文件内容的场景比如想先看看转换结果再决定是否覆盖。注意-n要求后面接两个文件名先输入再输出。-k在做批量归档或对时间戳敏感的操作时很有用否则转换后文件mtime会变成当前时间可能导致Makefile、rsync之类的工具误判文件状态。-c参数支持的转换模式包括ascii、7bit、iso、mac等。日常接触最多的是默认的ascii模式把DOS格式转为Unix格式mac模式是为了兼容上古时期单独用CR做换行符的Mac文件现在基本用不到了。3.3 反向操作unix2dos装了dos2unix通常也会带上unix2dos这个反向命令。把Linux下的文件转成Windows可读的CRLF格式$ unix2dos notes.txt unix2dos: converting file notes.txt to DOS format...这个场景不算高频但真有需要时很好用。比如要给Windows同事提供一份他们用记事本打开不会乱成一行的配置文件或者要把脚本上传到Windows版CI机器上执行。一个unix2dos就解决了不用费劲在sed里写转义。3.4 用-i查看文件换行符信息我特别推荐dos2unix -i这个参数它不做转换只输出文件当前的换行符统计信息$ dos2unix -i test.sh 5 3 0 no_bom text test.sh这个输出里第一列是LF数量第二列是CRLF数量第三列是CR数量接着是BOM状态和文件类型最后是文件名。怎么看CRLF数量大于0说明文件需要转。no_bom表示文件没有UTF-8 BOM头。这个命令在批量检查文件时非常高效不用一个个打开文件看。4. 批量转换的实战组合拳4.1 三条批量命令单个文件转换很简单但真实场景里往往是一整个项目、几百个文件都要处理。直接敲dos2unix一个个来不现实组合命令才是正道。最常用的三条# 方式一-exec 直接执行可靠但稍慢 find . -type f -name *.sh -exec dos2unix {} \; # 方式二xargs 批量处理速度更快 find . -type f -name *.conf | xargs dos2unix # 方式三处理带空格的文件名推荐 find . -type f -name *.txt -print0 | xargs -0 dos2unix第三种方式用-print0配合-0以空字符作为文件名分隔符即使文件名里带空格或特殊字符也能正确处理。如果文件名比较规范前两种也够用。实测下来处理几百个小文件时xargs比-exec快不少因为-exec每个文件都要启动一次外部程序xargs可以把一批文件名一次性传给dos2unix。4.2 只转换真正需要的文件批量转换最大的风险是把不该转的文件也转了尤其是二进制文件。有个简单技巧先用file命令筛选出带有CRLF标识的文本文件再转。find . -type f -exec file {} \; | grep CRLF | cut -d: -f1 | xargs dos2unix这行命令把当前目录下所有文件用file过一遍把输出里提到CRLF的文件路径提取出来再交给dos2unix处理。比盲目全量转换安全得多。还可以用grep -rIl筛选包含\r的文本文件。-I让grep把二进制文件当成不匹配-l只输出文件名grep -rIl $\r . | xargs dos2unix注意$\r这个写法在bash里是回车字符的转义表示能匹配行尾的CR。这个命令会把当前目录下所有含CR的文本文件找出来转换。慎用因为会递归处理所有子目录。而我个人最常用的一招是先用dos2unix -i把检查结果导出人工过一眼再决定find . -type f | xargs dos2unix -i | awk $2 0 {print $NF}awk只筛选第二列CRLF数量大于0的文件$NF取最后一列的文件名。这样先列清单再批量转稳妥。4.3 融入项目工作流批量转换命令用在临时场景没问题但长期维护项目时最好把换行符检查固化到工作流里。我自己的做法是在项目根目录放一个unixify.sh脚本#!/bin/bash # 统一当前项目下文本文件的换行符为 LF find . -type f \( -name *.sh -o -name *.conf -o -name *.txt -o -name *.md -o -name *.env* \) \ -exec dos2unix -q {} \;在git仓库里还可以加一个pre-commit钩子提交代码前自动转换暂存区里的CRLF文件#!/bin/bash # .git/hooks/pre-commit files$(git diff --cached --name-only --diff-filterACM | grep -E \.(sh|conf|txt|md|env.*)$) if [ -n $files ]; then echo $files | xargs dos2unix -q git add $files fi这样团队成员从Windows提交上来的文件会被自动纠正换行符避免^M污染仓库历史。5. 没有dos2unix怎么办替代方案5.1 sed与tr的快速转换有些精简环境或容器镜像里没有dos2unix也不能联网安装。这时候用现有的文本处理工具也能完成任务。最经典的是sed# GNU sedLinux 常见 sed -i s/\r$// file.txt这个正则把每行结尾的\r删掉。注意两个坑第一macOS自带的BSD sed要求-i后面必须跟一个参数写成sed -i s/\r$// file.txt第二如果文件里某一行中间有\r这个命令不会处理但文本文件里\r集中在行尾所以问题不大。tr是另一个常用选择tr -d \r windows.txt unix.txt-d参数表示删除指定字符。这条命令会把输入里的所有\r全部删除然后写到一个新文件里。优点是简单直接、速度极快缺点是如果文件里其他位置有\r也会一并删除而且必须用重定向产生新文件原来的文件需要手动替换。5.2 vim与perl/awk方案vim用户有更直观的方式。打开文件后执行:set ffunix :wqff是fileformat的缩写设为unix后vim保存文件时会统一使用LF作为换行符。如果文件里有少量CRLFvim在打开时状态栏会显示[dos]一眼就能发现。这个方法适合单个文件批量操作效率不如命令行。perl适合写进脚本或处理复杂场景perl -pi -e s/\r$// file.txt-p让perl逐行处理输入并自动输出-i表示原地修改-e后面跟的是处理逻辑。awk也能做awk { sub(/\r$/, ); print } windows.txt unix.txtawk的sub函数把行尾的\r替换为空再输出到新文件。原理和tr类似但更精细——只删除行尾的\r不影响行中间的内容。5.3 各方案对比方案命令示例优点缺点dos2unixdos2unix file.txt专门工具、功能完整、自动跳过二进制文件需要安装sedsed -i s/\r$// file.txt系统自带、正则灵活BSD和GNU版本差异对行中间的\r无能为力trtr -d \r in out简单、速度快删除所有\r需重定向可能误伤vim:set ffunix可视化、直观适合单文件不适合批量perlperl -pi -e s/\r$// file.txt跨平台、处理灵活需要perl环境awkawk { sub(/\r$/, ); print }文本处理强、可扩展需要重定向生成新文件从长期使用的稳定性来看我还是建议优先装dos2unix。它专门处理换行符问题对文件类型检测更完善不会像tr -d那样可能在二进制文件上闯祸。备用方案的价值在于救急不能替代专业工具。6. 边界情况与易踩的坑6.1 BOM的增删问题UTF-8 BOMByte Order Mark字节序标记是文件开头多出的三个字节EF BB BF用于标记文件采用UTF-8编码。Windows上的记事本、部分编辑器保存UTF-8文件时会自动加上BOM。这里有个关键细节dos2unix在默认转换过程中会把UTF-8 BOM头删掉。这对Linux环境其实是好事因为BOM会让shell脚本第一行的#!/bin/bash多出不可见字符还会让nginx、PHP等解析器报错。但如果你在做一个需要保留BOM的文件比如某些Windows兼容的配置文件或CSV导出就要用-r参数dos2unix -r file.csv反过来如果文件没有BOM而你偏要加可以这样dos2unix --add-bom file.txt实际操作中我见过不少人用dos2unix转完发现文件开头多了个\ufeff一样的乱码其实就是BOM的显示效果。判断文件有没有BOM用file命令看编码说明或者xxd file.txt | head -1看前三个字节是不是efbbbf。6.2 软链接与二进制文件保护dos2unix在默认情况下会检测文件类型跳过二进制文件。但如果你确定文件是文本且需要强制转换可以用-fdos2unix -f file.bin这个参数不能滥用。二进制文件里的字节可能恰好包含0D 0A序列强制转换会把它们变成0A直接损坏文件。我处理日志文件、数据dump的时候就遇到过这种情况发现转完文件大小缩水再对照十六进制才反应过来。软链接也要注意。运行dos2unix时如果文件是软链接不同版本的处理策略不太一样。某些旧版本会穿透链接去改目标文件某些版本可能报错。最稳妥的做法是先用readlink确认目标路径再对真实文件执行转换。要是担心误操作先复制一份再转或者用-n参数输出到新文件升级成两步走方案。6.3 编码问题别甩锅给dos2unix经常有人问我用dos2unix转换后中文还是乱码是不是工具没用好真不是。dos2unix只处理换行符不涉及字符编码。如果一个文件是GBK编码的中文文本dos2unix转换后换行符是LF了但中文依然是GBK字节。在UTF-8环境下显示本来就会乱码。遇到这种情况应该先处理编码再处理换行符。推荐顺序是先用iconv把GBK转成UTF-8再用dos2unix修正换行符iconv -f GBK -t UTF-8 old.txt new.txt dos2unix new.txt这样两步走才能同时解决乱码和换行符两个问题。习惯上我也会在编码转换前先确认源文件编码file命令能给出比较靠谱的提示。6.4 实践中的几个小技巧最后分享几个我实际处理换行符问题时总结的小技巧。第一拿到陌生文件先别急着转先用file命令和cat -A看一眼确认是不是CRLF、有没有BOM、是什么编码。三秒钟的判断能避免后面的折腾。第二转换完留个心眼用od -c看文件尾部$ od -c file.txt | tail -3如果能看到\n结尾而不是\r \n说明转换成功。这个检查在批量处理大量文件时尤其有用防止漏转。第三在Windows上写脚本时尽量从一开始就用支持Unix换行符的编辑器比如VS Code设置files.eol: \nGit Bash里配置core.autocrlf input从源头杜绝CRLF文件的产生。第四别忽略dos2unix -q参数的价值。批量处理时默认的converting file...输出会刷掉一屏幕加上-q安静模式之后只有真正出错才会打印信息日志干净得多也方便脚本里判断执行结果。第五归档长期保存的文本文件建议一律统一成LF这是最没有兼容性负担的格式。总有一天你要把这些文件从一台机器搬到另一台机器LF会帮你少踩无数坑。
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门