前言

之前我回顾了「#」号的补充历史,这期来看看另一种符号:「斜杠」。

实际上我们在键盘上能看到两种「斜杠」:「/」和「\」。不过,虽然同属「斜杠」且长相相似,但这俩「兄弟」的「经历」和「性格」可是截然不同:「/」历史悠久且老实本分;而「\」尽管出现时间不早,它引来的问题与麻烦却一点也不少。这篇文章将回顾这俩「兄弟」各自的历史,试图探究造成两者不同「性格」和经历的原因。

称谓说明

先来说说「/」「\」的称谓问题。「/」与「\」的一种常用称谓是「正反斜杠」,但我在网上用「正反斜杠」指代两者时就有人提出疑问:「凭什么「\」叫「斜杠」啊?我平时写「\」挺顺的啊,它就不能是正吗?」对此我也只能说感到很无奈——这词也不是我发明的啊!那么究其根源还是时间先后的问题。「\」的历史1(后面我会详细讲)只有短短几十年,此前很长一段时间都只有一种斜杠「/」。「\」出现后,为了与「\」区分才有了诸如「正向/反向斜杠」「前斜/后斜斜杠」之类的称谓。国内为了更好区分两者也有用「撇(/)捺(\)斜杠称谓的。

ASCII 与 Unicode 对「/」「\」的命名

一、本分的「/」

正如我上文所述,在西方,先出现的是「/」方向的斜杠。至于为什么是这样,我还没找到确切的答案,有一种可能是因为拉丁字母的斜体书写采用的是「/(向右前倾式)」方向,斜杠的书写自然顺应了这种方向。

拉丁字母(如英语)斜体书写

而对于古代中文来说,因其采用了竖排+从右往左的书写方式,因而并没有对斜杠的方向有所限制(事实上也确实如此,中国古代出现了两种不同方向倾斜的类似「斜杠」的符号:逗号「,」和顿号「、」,这两个符号早在汉代(可能更早)便已出现)

(至于阿拉伯语等从右向左书写的语言如何呈现「斜杠」,我目前还没找到有价值的信息,如果有读者了解这方面的信息欢迎来评论区与我交流。)

1.作为标点

在西方,「/」最初作为十二世纪斯巴达标点符号系统的一部分出现,该系统由 12 世纪一位名叫 Buoncompagno da Signa 的意大利文人发明。其中「/」被称为「直立 virgule(来自拉丁语virgula,表示「树枝」)」,表示短暂的停顿;而「—(被称为「水平 virgule」)」 则表示较长的停顿。

随着时间的推移,表停顿的斜杠「/」演变为逗号「,」(而在法语中,逗号仍被称为 la virgule)。而「/」本身又引申出了多种含义,比如分隔诗行(如:轻轻的我走了 / 正如我轻轻的来);进而可表示分隔选项(即「或」,如「是/否」),一些情况下也可表示「与」,如「海明威/福克纳一代」(后两种情况常用于非正式写作中,而在一些正式写作中不被提倡)

2.数学

「/」的另一个用途是表示「分数线(如 1/2)」,这可能是因为水平分数线(—)不便于印刷而产生的替代方案。

「/」由「分数线」进而引申为「除号」,相当于除法符号(÷)。英国数学家德摩根在19世纪中叶提倡使用「/」表示「除号」。

数学符号的 ISO 80000-2 标准建议仅使用「/」或「:」表示除法,而「不应该使用 ÷」。

3.货币

在英国(等采用同样货币单位的国家)「/」是「先令」的缩写,这种写法可能演变自「ſ(长 S)」。而「/」本身被称为「Solidus」,这一名称来自晚期罗马帝国和拜占庭帝国发行的金币(Solidus 来自拉丁语 Solid,意为「固体」),该种货币对欧洲其他一些国家的货币也产生了一定影响。

「Solidus」也是 Unicode 中「/」的正式名称。

Solidus 金币
采用「/」写法的门票价格告示,1946年
Unicode 中对「/」的命名

4.到了现代

在计算机中,Unix & 类 Unix 系统(Windows 许多情况下也支持)使用「/」分隔路径(关于文件路径的历史我在 另辟蹊「径」,看操作系统的发展 一文中有详细介绍)。万维网出现后,网址(URL)中也采用了类似设计,以「/」分隔目录;「万维网之父」蒂姆·伯纳斯·李 (Tim Berners-Lee) 后来表示,对出现在网址开头的「//」表示半开玩笑的歉意,称其「毫无意义且不必要」。

20 世纪 60-70 年代的 DEC 操作系统则采用「/」作为「选项字符」(switch character,用于分隔命令选项参数的字符),这一特性后来被 DOS & Windows 继承。CP/M 也在 CP/M Plus 版本中规定「/」为「选项分隔符」

DEC TOPS-10 命令
CP/M Plus 手册(1983)

C/C++、Java、JavaScript 等编程语言以「//」表示单行注释的开始,「/*……*/」则表示多行注释

而近年来流行的「斜杠青年」一词,来自《纽约时报》专栏作家 Marci Alboher。她发现,在纽约很多人拥有不止一个身份,他们在介绍自己的时候通常用「/」来区分不同的身份。于是她写了一本为《One Person/Multiple Careers: A New Model for Work/Life Success》的书来表述这种现象,书中她称这些人为「斜杠青年」 (Slasher)。

而「/」无论衍生出多少意义,大多数都没离开它的本义「分隔」,而「/」也没有脱离自己的这一本性。


接下来,来看看另一种斜杠:「\」。

二、不安分的「\」

1.初现

资料显示,截至 2022 年 11 月,能找到有关「\」出现在 20 世纪 60 年代前的信息依然很少。迄今为止发现的最早的资料是 1937 年 Teletype Corporation 的维护手册,其中展示了Teletype Wheatstone Perforator 键盘。在这里「\」被称为「对角键(Diagonal Key)」。

20 世纪 30 年代的 Teletype Wheatstone Perforator 键盘,「\」位于第三行末尾

直到 1960 年 6 月,IBM 发布了「扩展字符集标准」,其中才出现了「\」的影子(位于 0x19 处)。1961 年 9 月,IBM 的 Bob Bemer 向 X3.2 标准委员会提议将「\」纳入拟议标准,以支持 ALGOL 编程语言的逻辑运算符(/\,表示「与」)和(\/,表示「或」)。该提议后来得到了采纳,「\」进入了 ASCII,Bob Bemer 也因此被称为「「\」之父」。

Bob Bemer


 

IBM 的拓展字符集标准,1960 年 6 月
ASCII,1963 年


从此,「\」踏上了它不安分的旅途。

2.消失

尽管「\」成为了标准字符,但不代表所有字符集都接受了它。就连 ASCII 自己在后来都想把它换掉。原因很简单:「\」实在是一个太新的符号,应用范围实在是太少。

几种 ASCII 的修改方案,原来「\」对应的 0x5c 点位被其他符号替换

其结果是,后来的 ISO-646 标准将 0x5C 定义为「可本地化」代码点之一,也就是说,这个点位可以由不同国家或地区根据需要显示为不同的字形。

不同字符集对「\」的替换

1969 年,日文字符集 JIS X 0201 开始制定,那时候的电脑尚不具备处理双字节字符的能力,所以只能先引入半角的片假名,但代价就是,单字节码位不够了,于是就替换了一些当时还不常用的符号,比如日元符号「¥」就取代了「\」。

JIS X 0201

下面两图为 PC-8000 系列(1979 年推出)的键盘,日文版本(图1)第一行末尾的键位为「¥」,销往美国和加拿大的 PC-8001A 型号(图2)对应的位置则为「\」。

PC-8001(日文版本)
PC-8001A

后来的 Shift_JIS 也包含了 JIS X 0201 的单字节字符集。这一字符集后来衍生出多个版本,比如 Windows Code Page 932(也称为 Windows-31J),MacJapanese 等。

Shift_JIS 单字节字符集

其他字符集可能也出于类似的原因(由于代码位比较紧张,需要以当地的常用字符替换原来相对来说不常用的字符)也替换了「\」。

不过到了后来,ISO 8859 推出后,随着对一些字符的支持得到了完善,一些字符集(比如许多欧洲语言)又将 0x5C 替换回了「\」,但日韩仍然保留了原来的符号。(可能是因为 ISO 8859 并未对 CJK 字符做出规定)

3.麻烦

就在日韩等国替换「\」的时候,「\」又被赋予了新的含义。

在许多编程语言(例如 C、Perl、PHP、Python、Unix 脚本语言)和许多文件格式(例如JSON)中,「\」表示转义字符(也称为跳脱字符,escape character),即指示「应特殊处理其后面字符」的字符,如「\n」表示「换行」,「\t」表示「制表符」等等。

而「转义字符」的概念最初来自上文提到的 Bob Bemer,他设计了如今在我们键盘上常用的「Esc」键,最初目的是在不同机器码之间切换,后来又引申出「退出当前环境」。而「转义字符」最初指的是 ASCII 中的「Esc」控制字符(位于 0x1B 处),后来也引申为「标志着一个转义序列(需要特殊处理的字符序列)开始的那个字符」,比如许多编程语言中的「\」,以及网址中的「%」,等等。

至于为什么选用「\」作为转义字符,我还没找到确切的答案,可能是因为「\」在当时用途较少,选用「\」作为转义字符不会导致与该字符本身的含义相冲突。也有可能是因为推荐「\」进入 ASCII 的也是 Bob Bemer(虽然目前我并未找到证据表明 Bob Bemer 最早选用「\」作为转义字符的相关信息)

ASCII,1967年
部分转义序列

在 DOS & Windows 中,「\」是默认的路径分隔符。具体原因我已在 另辟蹊「径」,看操作系统的发展 一文中有详细介绍。

在 DOS & Windows 中,「\」是默认的路径分隔符

让人始料未及的是,「\」的使用产生了许多问题,这些问题被统称为「0x5C 问题(以它的 ASCII 码位命名)」。

第一种问题(也可以说是「特性」)就是「\」的显示问题。正如我前一部分所述,一些语言字符集中会将「\」对应的 0x5C 点位替换为其他字符。于是,在日文/韩文版的 Windows 中,文件路径分隔符会显示为各自的货币符号「¥/ ₩」。即使到后来系统已经支持 Unicode 编码,为了保证兼容性,微软依然选择货币符号作为 0x5C 日韩文的字符映射。前微软开发人员 Michael S.Kaplan 解释说:

这其实很糟糕,但改变它会破坏非 Unicode 应用程序中的所有路径。Windows 将永远不会以日语或韩语完成启动!

日文 Windows 的文件路径
韩文 Windows 的文件路径
为了保证兼容性,微软依然选择货币符号(¥/ ₩)作为 0x5C 日韩文「最适合」字符映射

这还影响了「\」作为转义字符时的显示。这里展示了一种 0x5C 既用于日元符号又用于转义字符的极端情况:printf("¥¥%d¥n", price)(一般情况下应是 printf("¥¥%d\n", price))


而另一种「0x5C 问题」则是,在包含双字节字符的字符集中,如果使用的编译器不支持这些字符集,那么它可能会将一个双字节字符拆成两个单字节进行解释。如果字符第二个字节为 5C 2,便可能会转为「\」,而又由于「\」属于转义符,这一问题还会对编程等使用环境产生影响。例如,下面是一段用 Shift_JIS 编写的代码:

func_x();  // x機能
if (mode > 0) { stop_x(); }

不支持 Shift_JIS 的编译器将「能」第二个字节中的 0x5C 视为转义字符「\」,并对行尾的换行符进行转义:

func_x();  // x機?\?if (mode > 0) { stop_x(); }

结果,第二行也被注释掉了,程序因此无法正常运行。

一个距现在比较近的例子是,2023年4月,有网友发现在 Compile Heart 的 Dokapon Kingdom 游戏中,当保存或退出游戏时,如果遇到包含「ソ(罗马音:So)」的角色或账号名称时,屏幕会卡死。这便是 0x5C 问题带来的影响。

关于该 bug 的推文

而在繁体中文使用的大五码(Big5)中,这一问题常被称为「許功蓋」问题(取自大五码中字符第二位结尾为 5C 的 3 个常用字)

「許功蓋」问题中涉及到的字符

简体中文的 GB2312 字符集为了避免与 ASCII 字符编码相重,两位字节都选择避开 00-7F 范围。但 GBK 为了收录更多字符(包括繁体字),第二位字节扩展为 40-FE,便有几率产生「0x5C 问题」。

虽然 GBK 下结尾为 5C 的字符大部分不是常用字,但也在其他一些符号上「栽了跟头」。因为只要第二位字节在 40-7F 范围内便有可能转为对应的 ASCII 字符,从而造成解释错误。比如,「东方 project」有时会莫名显示为「朹方 project」,原因是「东」的 GBK 编码为「967C」,其中第二字节 0x7C 恰与 ASCII 的「|(0x7C)」相同,于是一些不支持 GBK 的程序会误以为它是 Windows 文件名禁止出现的字符「|」,就将其替换为下划线「_(0x5F)」,字节也换成了 0x5F,于是「东(967C)」就变成了「朹(965F)」。

4.解决

对于第一类问题,解决办法有这么几种(来自 Firefox 1.0 RC 前版本中「\」在日语语言环境中呈现为 ¥ ):

  • 永远不替换 「\(0x5C)」到「¥(U+00A5)」
  • 在编码为 Shift_JIS、EUC-JP 或 IS0-2022-JP 情况下进行替换
  • 由用户决定是否替换

而 Firefox 最终选择的解决方案不在这三种内,而是限定了语言、替换选项和字符集非Unicode 时才进行替换(以减少 false positive 误报)。

而现在,许多编程平台在进行国际化编程时都会存在特定语言的 Unicode 勘误(collation)功能,以减少这类问题的影响。

对于第二类问题,一种解决方法,是在结尾为 5C 的字符后添加「\」,因为「\\」会被解释为「\」,从而保证前面的字符被正确解释。但是这样可能会产生新问题:有些程序或网页并不会把「\」当作特殊字符看待,所以可能会在后面错误地多显示了「\」。

随着 UTF-8 编码方式的逐渐普及,越来越多的人选择采用 Unicode 编码(UTF-8 属于 Unicode 编码的其中一种实现方式)以避免此类问题。

总结

从上文的讲述中,我们可以看出这俩「兄弟」性格的不同:「/」老实本分,且「技多不压身」;而「\」却非常不安分,虽然使用范围不及「/」广,但带来的问题却远远多于「/」。对于其背后的原因,以下是我的一点个人理解:

首先,斜杠以其本身的简单性(只需信手一划),应该会随着文字的发展而自然出现。至于哪种倾斜方向的「斜杠」会得到普及,个人认为与所使用语言的书写方向有关。正如前文所述,西方语言多采用从左往右的书写方式,尤其是斜体书写采用的是「向右前倾式」方向,因此「/」方向的斜杠得到了普及。而对于古代中文来说则没有这种限制,也就同时存在两种不同倾斜方向的类似「斜杠」的符号:逗号「,」和顿号「、」,这两个符号早在汉代(可能更早)便已出现。

而又因「/」其天然带有「分隔两侧」的「属性」,因此尽管「/」随着时代的推移含义越来越丰富,但大部分含义都没离开「/」用于「分隔」的本义,可变性比较有限。


相比之下,(从西方语言的角度看)「\」的出现则是「不自然的」。因此「\」只能是为了实现特殊用途才会出现。

不过,我们日常使用的符号中也有些不是「天然」产生的,那为什么「\」的使用会带来问题呢?个人认为原因在于,「\」出现的时间,正是世界各个地方联系越来越紧密的时代,对信息交流的标准化要求自然也越来越高。而「\」初来乍到,用途十分有限,因而对它的标准化要求也不是很重视,也因此,在「\」的用途得到扩展后,造成了一些问题和混乱。

但换个角度看,这种「新」既是问题,又何尝不是意味着机会的存在?正如 Bob Bemer 当年所说:「「\」是最有用的字符之一——几乎没有人使用过或抢占的字符,随时等待新的用途。(the backslash became that most useful of characters -- one nobody had used or preempted, just waiting there for a new use. )」所谓是「一张白纸好做文章」,与历史悠久的「/」相比,「\」的使用能够展现出更强的灵活性。随着时间的推移,可能「\」能跳出「斜杠」作为「分隔」的「本性」,探索更多新的可能,获得更多新的意义。而「\」的经历也为我们接下来引入新字符提供了教训:需要做好这个符号的标准化工作,确保它在工作时不会因为标准不同而引起冲突。


以后,有人再跟你说「想做一个斜杠青年」时,你便可以问,「想做哪种「斜杠」?」

-END-

参考资料

  1. 斜杠 - 维基百科
  2. 为什么中国的文言文没有标点符号? - 知乎
  3. # 等统治 Twitter(和网络)的其他 6 个符号的历史 - 时代周刊
  4. Miller, Jeff - 分数符号探源
  5. 基思·休斯顿 - 标点符号的神秘起源 - BBC
  6. 反斜杠 - 维基百科
  7. 为什么「\」被称为后斜斜杠,尽管某种意义上也可以说它向前斜 - StackExchange
  8. Bob Bemer - ASCII 是如何得到「\」的
  9. Shift_JIS - 维基百科(日文)
  10. Shift_JIS - 维基百科(英文)
  11. 转义字符 - 维基百科(中文)
  12. Bob Bemer - Escape 按键
  13. 转义序列 - IBM
  14. Unicode 的日元符号问题 - 维基百科(日文)
  15. 日文编码下反斜线是¥的一些考据
  16. Shift_JIS 0x5C 問題
  17. 大五码 - 维基百科(中文)
  18. 什麼是「許功蓋」問題?
  19. GB2312 编码表
  20. Michael S. Kaplan - 什么时候反斜杠不是反斜杠?
  21. Michael S. Kaplan - 终于,合理地解释了日元/韩元/反斜杠问题