为什么 ASCII 中没有分隔符?

Dav*_*ave 28 csv text-delimiter

我的问题是:为什么没有特定的“分隔符”字符?一种将用于所有类型的定界。我们有用于换行、打印设置等的特殊字符...

如果这些是常见的文本字符,为什么我们有时会使用逗号、空格、制表符等。这背后有历史吗?就像他们在制作 ASCII 等时可能不需要分隔符一样?

(对我来说似乎有意义:有一个特殊的分隔符,它的唯一目的是在需要时“分隔”单独的值)

Kel*_*ari 80

分隔符已存在于ASCII 中。十进制 28-31(十六进制 1C-1F)是分隔符。这包括文件、记录、组和单位分隔符。

我假设我们不使用它们,因为输入不需要多个键来输入单个字符的键盘字符会更容易。这也允许不同格式之间更容易的交换。逗号分隔值几乎适用于任何系统,无论是否符合 ASCII。

  • 逗号分隔值在欧洲不好用,我们有 **十进制逗号** https://en.wikipedia.org/wiki/Decimal_separator#Countries_using_decimal_comma Microsoft 使用制表符作为剪贴板上的字段分隔符效果很好,但在文件中不是标准的。 (47认同)
  • @grahamj42 对“在欧洲不好用”的一种回应是在“ASCII”中扩展“A”:-) (17认同)
  • ASCII 来自 60 年代,当时电传打字机风靡一时(这就是为什么 7 是 BEL)。那时我认为大多数(如果不是全部)操作系统都希望文件具有记录、模式并且像访问数据库中的行一样工作。UNIX 出现在 70 年代,它采用了简化的“越糟越好”的理念,并且基本上引入了这样一种概念,即一切都应该是人类可读的文本,文件和 I/O 只是一个字节流,单个程序负责文件结构. 所以我认为在那个时候特殊分隔符的想法已经过时了。 (13认同)
  • 另请注意,大多数文本编辑器不会*显示*这些字符中的任何一个(或在某些情况下仅选择性地显示它们),因此虽然这对计算机阅读很好,但对其他人来说却不太好。 (10认同)
  • 实际上,我曾经编写过使用 ASCII 分隔符的程序,我记得这些分隔符最初是用于磁带格式化的。问题是,它们不能很好地处理二进制(C 的以空字符结尾的字符串共有的问题),所以它们很快就被淘汰了。 (9认同)
  • @grahamj42 大多数使用 CSV 文件的软件也可以选择使用 TSV 文件。 (6认同)
  • @LawrenceC 实际上,ASCII 中的控制字符是为面向块的终端和计算机之间的协议设计的,而不是为电传打字机设计的。您可以从 Teletype 键盘生成大部分(如果不是全部)ASCII 控制字符,但 KSR33 可以解释其中的很少一部分:但是我已经花时间研究“智能终端”协议,在这些协议中它们被大量使用。话虽如此,没有定义既可供用户访问又保留为分隔符的字符,我认为这正是 OP 所要求的;这样做将是徒劳的,因为有人会立即重新利用它。 (6认同)
  • 虽然“美国”确实指的是北美 + 南美,但现实情况是,当全世界的人们在地缘政治(而不是纯粹的地理)意义上使用简称“美国”时,他们指的是“美国* *美国**”。无论如何,**A**SCII 的来源不是来自外部而是来自内部 - 来自 [美国国家标准协会](https://en.wikipedia.org/wiki /American_National_Standards_Institute) 和位于美国并主要(当时)为美国服务的相关组织。我认为当时没有任何歧义。 (5认同)
  • @phuclv 美国有 3.3 亿人口,而北美其他地区的人口总数为 2.2 亿。你说的是哪个多数?如果您非常反对这个名称,请称之为 [ISO-IR-006](https://www.itscj.ipsj.or.jp/iso-ir/006.pdf)。 (2认同)

man*_*act 52

如前所述,ASCII 包含分隔符。问题在于在数据输入期间需要额外的键来包含分隔符 - 对于大写字母或其他特殊的可打印字符(例如 !@#$),Control 并不比 Shift 更难使用。问题是传统上那些控制字符不是直接可见的。即使制表符、回车和换行 - 产生即时动作,也不会产生可见输出。

您无法区分制表符和空格之间的电传打字机。您无法区分换行符和空格到行尾+换行到下一行。同样,分隔符没有定义的可打印图像。它们可能会显示在一些(现代)文本编辑器中,并且它们可能会在各种设备中产生即时动作,但它们不会留下痕迹。

如果数据仅设计为机器可读——即我们通常所说的二进制文件,所有这些都无关紧要。但是用于数据输入和系统之间传输的文本通常是有意为人类可读的。如果它是人类可读的,分隔符需要是可打印的。

  • 这才是真正的原因。即使人们想出了一个分隔符,大多数人仍然会使用他们熟悉的东西(逗号、JSON、XML),因为数据是源代码,而不是可执行代码。这实际上是历史上发生的事情,因为分隔符从一开始就存在,但人们忽略了它们并为数据发明了人类可读的语法 (16认同)
  • 我实际上不得不处理使用 1C-1F 字符的文件。它们用于某些标准。编辑器真的没有理由不能为这些值显示一些符号,但他们通常不会。 (6认同)
  • 这有点像第 22 条军规。为了使分隔符成为人类可读格式的一部分,它必须是可见的。但如果它是一个可见的字符,*某人*会在他们的文本中使用它,可能是因为您认为单个字符串本身实际上被分解为多个部分(请参阅 Kaz 的答案)。 (4认同)

Ste*_*nny 16

正如另一个答案中提到的,ASCII 确实有分隔符。看这里 [1],提到了这些:

代码点 姓名
U+001C 文件分隔符
U+001D 组分隔符
U+001E 记录分隔符
U+001F 单位分隔符

这些都被使用了。例如,U+001C(八进制 34)是SUBSEPGNU AWK的默认[2] 字符串。

  1. https://wikipedia.org/wiki/ASCII#Control_code_chart
  2. https://gnu.org/software/gawk/manual/html_node/MultiDimension

  • 还有标题的开始,文本的开始和文本的结束。我想,传输结束也算数。 (6认同)
  • 使用 Unicode 代码点表示法 (U+) 引用 ASCII 代码是相当不合时宜的。 (3认同)
  • 我一直认为这些不经常使用是可悲的。我敢肯定,曾经有开发人员花了太多时间来弄清楚如何界定事物,而且每个人都发明了自己的方式。`+`、`|`、`、`、`;` 等。 (3认同)

小智 10

这主要是历史性的。

在信息学的旧时代,数据文件大多是固定宽度字段文件,因为它是 Fortran IV 和 COBOL 等语言的自然 IO:第一个字段为 n 个字符,第二个为 m,等等。

然后 C 语言提供了scanf在(组)空格上分割输入的功能,人们开始对包含数字的数据文件使用自由格式。但是,当某些字段可能包含空格(scanf被称为穷人的解析器)时,这会导致结果混乱。因此,由于拆分的另一个标准函数是strtok使用单个分隔符,因此大多数(说英语的)人开始使用逗号 ( ,) 作为分隔符,因为在文本编辑器中手动编写逗号分隔值文件很容易。

然后国家语言支持进入游戏......在一些欧洲语言(法语)中,小数点是逗号。IT 人员习惯使用小数点,但很少有技术人员不习惯,因此法语版本的 Windows 开始将分号 ( ;)定义为分隔符,以允许在十进制数中使用逗号。

与此同时,有些人意识到,当字段的长度总是很近时,制表符(所有键盘上都存在)允许提供很好的垂直对齐,这就是第三种标准的原因。

最后,标准化开始成为事实,2005 年出现了 RFC 4180。它确实将逗号定义为官方分隔符,但由于 Windows 决定玩 NLS 游戏,想要处理真实文件的工具和库必须适应各种可能的分隔符。

这就是为什么在 2021 年,我们在 CSV 文件中有许多可能的分隔符的原因......


Kaz*_*Kaz 7

事实证明,在 ASCII中有一个事实上的通用分隔符:空字符。Unix 和 C 语言表明,您可以构建一个完整的平台,在该平台中,空字符从字符串中排除,在其表示中充当终止符。其他平台也纷纷效仿,如 Microsoft Windows。

今天,它几乎是铁一般的保证,没有文本数据包含空字节。如果数据包含空字节,则它是二进制而不是文本。

如果你想在字节流中存储一系列文本记录或字段,如果你用空值将它们分开,你几乎没有问题。空值不需要像转义这样的废话。如果有人过来说他们想在文本字段中包含一个空字节,您可以像喜剧演员一样嘲笑他们。

野外空值分离的例子:

  1. Microsoft 允许注册表中的项目是多字符串:单个项目包含多个字符串。这被存储为串联在一起的空终止字符串序列,并带有一个额外的空字节来终止整个序列。如 in"the\0quick\0brown\0fox\0\0"表示字符串列表"the", "quick", "brown", "fox"

  2. 在 Linux 内核上,每个进程的环境变量都可以通过/proc文件系统获得,如/proc/<pid>/environ. 此虚拟文件使用空分隔,如PATH=/bin:/usr/bin\0TERM=xterm\0....

  3. 一些 GNU 实用程序可以选择生成空分隔的输出,这正是允许它们用于编写更健壮的脚本的原因。GNUfind有一个-print0用空终止而不是换行分隔来打印路径的谓词。这些路径可以xargs -0从其标准输入中读取空分隔的字符串,并将它们转换为指定命令的命令行参数。此组合将绝对传递所有文件名/路径,而不管它们包含什么:因为路径不能包含空字节。

为什么我们玩游戏与其他分离?制表符、逗号、分号等等,而不是仅仅使用 null?问题是我们需要多层次的分离。好的,所以 null 可靠地将字节流切割成文本。但在这些文本中,可能需要另一个级别的划界。有时会发生单个字符串内部具有更多结构的情况。路径包含用于分隔组件的斜杠。MAC 地址使用冒号分隔字节。诸如此类的事情。电子邮件地址具有多级嵌套定界,例如local@domain围绕@符号,然后域部分用点分隔。那里允许使用括号之类的东西%和之类的!. 人们编写字符串处理代码来处理这些格式,由于 C 和 Unix 的影响,字符串处理代码在很多语言中都不会像空字节那样。

使用空字节作为字段分隔符的 GNU Awk 演示,处理/proc/self/environ.

$ awk -F'\0' \
      '{ for (i = 1; i <= NF; i++) 
           printf("field[%d] = %s\n", i, $i) }' \
      /proc/self/environ
field[1] = CLUTTER_IM_MODULE=xim
field[2] = XDG_MENU_PREFIX=gnome-
field[3] = LANG=en_CA.UTF-8
field[4] = DISPLAY=:0
field[5] = OLDPWD=/home/kaz/tftproot
field[6] = GNOME_SHELL_SESSION_MODE=ubuntu
field[7] = EDITOR=vim
[ snip ... ]
field[54] = PATH=/home/kaz/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin:/home/kaz/bin:/home/kaz/bin
field[55] = GJS_DEBUG_TOPICS=JS ERROR;JS LOG
field[56] = SESSION_MANAGER=local/sun-go:@/tmp/.ICE-unix/1986,unix/sun-go:/tmp/.ICE-unix/1986
field[57] = GTK_IM_MODULE=ibus
field[58] = _=/usr/bin/awk
field[59] = 
Run Code Online (Sandbox Code Playgroud)

由于末尾的空字节,我们得到了一个额外的空白字段,因为 awk 将其视为字段分隔符,而不是终止符。然而,这正是可能的,因为 GNU Awk 允许空字节成为字符串的组成部分。-F '\0'根据 POSIX 规范,该参数不需要工作。POSIX 在题为“awk 中的转义序列”的表

\ddd一个字符,后跟最长的一个、两个或三个八进制数字字符序列 (01234567)。如果所有数字都是 0(即 NUL 字符的表示),则行为未定义。

因此,依靠 Awk 来分隔空字节上的字段或记录是完全不可移植的。这种语言问题可能是我们不更多使用空字符的原因之一。