程序员必须搞懂的换行符:\r\n\r\n 全解析

在编程、文本处理、Git、日志系统或网络协议中,经常会看到这些字符:

1
2
3
\r
\n
\r\n

很多人知道它们和“换行”有关,但为什么会有这么多种?不同系统为什么不一样?工程中应该怎么处理?

本文会从 历史 → 系统差异 → 开发实践 → 常见坑 → 通用技巧 全面讲清楚。


一、什么是 \r\n

在 ASCII 控制字符中:

字符 名称 ASCII 含义
\r Carriage Return 13 回到当前行开头
\n Line Feed 10 移动到下一行
  • \r:将光标移动到当前行最左边

  • \n:将光标向下移动一行

这两个字符来自打字机时代。当时打印一行文字需要两个动作:

  1. Carriage Return:打印头回到行首

  2. Line Feed:纸张向上滚动一行

因此两个字符组合才能真正开始新的一行。 (GeeksforGeeks)


二、为什么会有 \r\n

在早期打印设备中:

1
2
CR  → 回到行首
LF → 向下移动一行

如果只执行:

1
LF

打印头会停在原来的列位置。

例如:

1
2
Hello
World

因此很多系统使用:

1
CR + LF

也就是:

1
\r\n

表示真正的换行。 (维基百科)


三、不同操作系统的换行规则

由于历史演进,不同系统选择了不同的换行方式。

系统 换行符
Linux / Unix \n
macOS (OSX以后) \n
Windows \r\n
旧 MacOS \r

示例:

Linux

1
2
Hello\n
World\n

Windows

1
2
Hello\r\n
World\r\n

Unix 系统选择 只用 LF,因为系统会自动把光标移到行首。 (ToolSG)


四、\r 的特殊用途

\r 其实不一定用于换行,它经常用于 覆盖当前行内容

例如进度条:

1
2
3
printf("Progress: 10%%\r");
printf("Progress: 20%%\r");
printf("Progress: 30%%\r");

终端效果:

1
Progress: 30%

很多 CLI 工具(npm、wget、curl)都是这么实现进度刷新。


五、为什么会出现 ^M

如果在 Linux 中打开 Windows 文件,经常看到:

1
^M

例如:

1
2
Hello^M
World^M

原因:

1
2
Windows: \r\n
Linux: \n

Linux 会把 \r 当成普通字符显示。

解决方法:

1
dos2unix file.txt

1
sed -i 's/\r$//' file.txt

六、编程语言中的换行

大多数语言已经做了兼容处理。

C / C++

1
printf("Hello\n");

在 Windows 文本模式下:

1
\n → \r\n

自动转换。

但如果使用二进制模式:

1
fopen("file.txt","wb")

则不会自动转换。


Python

1
open("file.txt","w")

Python 会自动处理换行。

如果需要指定:

1
open("file.txt","w", newline="\n")

JavaScript / Node.js

通常直接使用:

1
\n

Node 在大多数情况下不会有跨平台问题。


七、网络协议为什么必须 \r\n

很多协议规定:

1
CRLF = 行结束符

例如:

HTTP

1
2
3
GET / HTTP/1.1\r\n
Host: example.com\r\n
\r\n

其他协议

  • SMTP

  • FTP

  • Redis Protocol

如果只写 \n,某些服务器会解析失败。


八、Git 的换行问题

多人协作时最常见问题之一。

例如:

1
LF → CRLF

Git 会提示:

1
LF will be replaced by CRLF

原因:

  • Linux 使用 LF

  • Windows 使用 CRLF

解决方案:

Windows

1
git config --global core.autocrlf true

Linux / macOS

1
git config --global core.autocrlf input

或者统一:

1
.gitattributes
1
* text=auto eol=lf

九、工程中最推荐的做法

现代软件工程基本遵循以下规则:

1 统一仓库换行

使用:

1
LF (\n)

原因:

  • Linux / macOS 默认

  • Docker / CI 默认

  • GitHub 默认


2 读取时兼容所有格式

解析文本时建议支持:

1
2
3
\r\n
\n
\r

例如 JS:

1
text.split(/\r?\n/)

3 写入时统一使用 \n

不要主动写:

1
\r\n

除非是:

  • Windows专用文件

  • 网络协议


4 编辑器统一设置

例如 VSCode:

1
LF

右下角可以切换:

1
CRLF / LF

十、开发中的几个经典坑

1 Shell 脚本无法执行

如果脚本是 Windows 换行:

1
#!/bin/bash\r

Linux 会报错:

1
bad interpreter

解决:

1
dos2unix script.sh

2 Docker 构建失败

Docker 容器通常是 Linux 环境:

1
CRLF → 执行失败

3 Git diff 全部变更

如果切换:

1
CRLF → LF

Git 会认为所有行都修改。


十一、最简单的记忆方式

1
2
3
4
\r   = 回车(回到行首)
\n = 换行(下一行)
\r\n = Windows换行
\n = Unix/Linux换行

开发原则:

1
2
3
读取:兼容所有
存储:统一 LF
协议:使用 CRLF

十二、一个非常实用的工程口诀

很多团队内部会用一句话总结:

1
读宽松,写统一,协议严格

意思是:

1
2
3
读取:兼容 \r\n \n \r
存储:统一 \n
协议:严格 \r\n


文章作者: kaxifa
更多内容: https://kaxifa.eu.org