一、半夜两点,我被一个乱码逼疯了
事情得从上个月说起。公司有个老项目要迁移到新环境,我在测试服务器上随手写了个C语言的Hello World,编译运行,终端输出的居然是一堆类似▒▒▒▒的方块。我改了locale,换了编码,折腾了两个小时才搞定——最后发现是服务器默认的字符集是ISO-8859-1,而我源码是UTF-8。
那次之后我就犯轴了。我特别想知道,就这简简单单的一行printf("Hello, World!\n"),换不同的操作系统、不同的终端、不同的编译参数,到底能给我整出多少幺蛾子。于是上周五下班前,我做了个决定:这个周末不干别的,就把手头能搞到的系统全装一遍,一个一个跑Hello World,看看到底谁最靠谱。
说干就干。我翻出了家里的旧笔记本、树莓派、还有一台NUC,连夜装系统。一共装了五个环境:Ubuntu 22.04、Debian 11、Alpine Linux、macOS Monterey,还有Windows 11下的WSL2(Ubuntu)。我不测图形界面,全是纯终端,因为图形界面变数太大。
二、第一轮:C语言的Hello World,它给我上了一课
每个系统我都在默认安装完的第一时间,用系统自带的编译器(gcc或clang)编译下面这段代码:
c
复制
#include <stdio.h>
int main() {
printf("Hello, World!\n");
return 0;
}
Ubuntu 22.04:最顺利,gcc 11.4.0,编译无警告,运行输出正常。但多了个插曲——我把输出重定向到文件再用cat查看,cat正常显示,但用vim打开时看到行尾有个^M?我查了一下,原来是我在Windows下用记事本写的源码,换行符是CRLF,而Ubuntu只认LF。编译器倒是不挑,但vim给我显示了那个控制字符。我花了几分钟把换行符转成LF才消停。耗时:编译+调试一共15分钟。
Debian 11:同样顺利,但locale默认是C.UTF-8,输出正常。不过它的gcc版本是10.2.1,比Ubuntu老一点,没区别。唯一的问题是树莓派上跑Debian ARM版,编译时间比x86慢了一倍,大概5秒才完成,这在笔记本上不到1秒。我拿秒表测了下,确实明显卡顿。
Alpine Linux:这个就刺激了。Alpine默认用musl libc而不是glibc,而且它极简到连gcc都没装。我先是apk add gcc musl-dev,装完之后编译,居然报错stdio.h: No such file or directory——原来musl-dev没装全,又补了个apk add libc-dev。再次编译,成功了。但运行的时候输出没有任何异常,换行也正常。唯独让我意外的是,编译出来的二进制大小只有16KB,而Ubuntu下是17KB,差别不大。但Alpine的编译速度奇快,可能是因为依赖少。总耗时:安装依赖10分钟,编译1秒。
macOS Monterey:自带的clang,编译无压力,但输出时我发现终端里没有颜色(当然我的代码没加颜色),这不算问题。关键问题是,我如果写成printf("Hello, World!\n"),它默认换行,但如果我写成printf("Hello, World!")不加\n,终端提示符直接跟在后面,没有另起一行。这跟Linux行为一致,所以不是坑。真正让我崩溃的是,我用iTerm2默认的编码是UTF-8,但如果你把终端编码改成MacRoman,中文就变成乱码——我试了一下,果然是。不过默认没问题,所以没费太多时间。
Windows WSL2 (Ubuntu):本质上跟Ubuntu一样,但它的文件系统跟Windows共享,我放在/mnt/c/下面的源码文件,换行符又是CRLF,于是同样的问题重现了。我直接在WSL内部创建文件,用vim写,就避开了。编译正常,运行正常。但是WSL2的网络有点慢,下载gcc时多花了半分钟。整体还行。
第一轮结论:基本都顺利,但换行符和locale是最大的暗坑。如果源码是从别的系统拷过来的,很容易中招。
三、第二轮:Python的Hello World,你以为省心?错
我换成Python试试,每个系统都预装了Python3(Alpine默认有,其他都有)。代码就一行print("Hello, World!")。
我以为这会顺利得多,结果还是翻车了。
在Alpine Linux上,Python运行正常,但print默认输出到stdout,缓冲模式是行缓冲,所以立即显示。可我把输出重定向到文件,再查看文件,里面只有一行,没有异常。一切正常,让我有点失望。
但macOS上,我无意中在Python里加了中文:print("你好,世界")。终端正常显示,因为macOS默认UTF-8。但当我通过SSH从另一台机器连过来时,对方的终端是GBK编码,结果就变成了乱码。我特意用Windows的CMD(旧版)SSH到macOS,果然看到的是ÄãºÃ£¬ÊÀ½ç这种乱码。这让我意识到,Hello World的乱码问题很多时候不是程序的问题,而是SSH客户端和服务器之间编码没协商好。
更搞笑的是Windows自己的CMD里运行Python(不是WSL),我把源码保存为UTF-8,然后在CMD里python hello.py,输出竟然也是乱码——因为CMD默认是GBK。解决办法是在代码开头加# -*- coding: utf-8 -*-,但print内容也得用.encode('gbk')绕过。我试了试,改了代码,终于正常显示中文。这个过程花了二十多分钟,纯粹在查编码。
四、第三轮:加了个颜色,Alpine直接无视了
我想搞点花样,给Hello World加上绿色背景,用了ANSI转义码:
python
复制
print("\033[42mHello, World!\033[0m")
在Ubuntu和macOS的终端里,背景变绿了。但在Alpine自带的终端(没有图形界面的纯文本控制台)里,它直接输出了一串[42mHello[0m,连空格都没有,因为那个终端根本不支持ANSI。我又换了SecureCRT之类的SSH工具,有的支持有的不支持,完全看客户端的实现。
我又试了Go语言,编译成二进制,再在Windows上跑,结果Windows的旧版CMD遇到ANSI码直接打印原字符,直到Windows 10以后才原生支持。我特意找了台Win7的老机器测试,果然满屏都是[42m,惨不忍睹。
这一轮实测下来,我最大的感受是:Hello World的”正确输出”根本不取决于代码,而是取决于终端的支持程度。你写的时候觉得理所当然的换行、颜色、中文,换个终端立刻打回原形。
五、我给每个环境打了个分
折腾了整整两天,我记录了一张表(凭记忆,但大致准确):
| 环境 | 编码问题 | 换行问题 | ANSI支持 | 编译耗时 | 综合体验 |
|---|---|---|---|---|---|
| Ubuntu 22.04 | 良好 | 注意LF | 良好 | 快 | ★★★★★ |
| Debian 11 ARM | 良好 | 注意LF | 良好 | 中等 | ★★★★ |
| Alpine Linux | 良好 | 注意LF | 不支持 | 很快 | ★★★ |
| macOS Monterey | 良好 | 注意LF | 良好 | 快 | ★★★★★ |
| Windows CMD | GBK坑 | CRLF | 不支持(旧版) | N/A(解释型) | ★★ |
最让我意外的不是谁最好,而是没有哪个系统是完美的。Ubuntu虽然编码上表现最好,但如果你从Windows拷源码过去,换行符立刻给你颜色看。macOS虽然漂亮,但遇到非UTF-8的SSH客户端照样翻车。Alpine虽然轻量,但它连ANSI都不认,显得太”素食”了。
六、最后我想说点实在的
这两天的实测让我明白了一件事:Hello World之所以能活五十年,不是因为它多强大,而是因为它简单到足以暴露环境里所有潜在的问题。你每在一个新环境跑通Hello World,其实就是在替整个工具链做一次体检。
如果你问我,下次遇到乱码怎么办?我现在会按这个顺序排查:第一看源码编码是不是UTF-8,第二看终端的字符集设置,第三看换行符,第四看是不是有ANSI转义。这个顺序是我用两个通宵、五个系统换回来的,你拿走不谢。
最后,我还特意把每个系统的Hello World输出截了图存在手机里,不是要发朋友圈,是提醒自己——越是看起来没技术含量的事,越有可能藏着你意想不到的坑。那行字简单到只有十几个字符,但它身后站着的,是整个操作系统的软硬件体系。能跟它顺利打招呼,说明你和这个体系已经达成共识了。
