一尘不染

argv的编码是什么?

linux

我不清楚在C的何处使用什么编码argv。我特别对以下情况感兴趣:

  • 用户使用语言环境L1创建名称为N包含非ASCII字符的文件
  • 稍后,用户使用区域设置L2在命令行上制表完成该文件的名称,该文件作为命令行参数输入到程序P中

P在命令行上看到什么字节序列?

我观察到在Linux上,在UTF-8语言环境中创建文件名,然后在(例如)zw_TW.big5语言环境中用Tab键完成该文件名似乎会使我的程序P喂UTF-8而不是UTF-8
Big5。但是,在OS X上,相同的一系列操作导致我的程序P获得Big5编码的文件名。

这是到目前为止我一直在想的事情(很长,我可能错了,需要纠正):

视窗

文件名以某种Unicode格式存储在磁盘上。因此Windows以这个名称命名N,从L1(当前代码页)转换为N我们将称为的Unicode版本N1,并存储N1在磁盘上。

然后,我 假设
发生的事情是,稍后稍后使用制表符补全时,该名称N1将转换为语言环境L2(新的当前代码页)以进行显示。运气好的话,这将产生原始名称N-但是,如果其中N包含L2中无法表示的字符,则该名称将不成立。我们叫新名字N2

当用户实际按下Enter键以使用该参数运行P时,该名称N2将转换回Unicode,N1再次产生。这N1是通过在UCS2格式现在可供程序GetCommandLineW/
wmain/ tmain,但用户GetCommandLine/ main会看到的名称N2在当前区域设置(代码页)。

OS X

据我所知,磁盘存储的故事是相同的。OS X将文件名存储为Unicode。

对于Unicode终端,我 认为
发生的事情是该终端在Unicode缓冲区中构建了命令行。因此,完成制表符后,它会将文件名作为Unicode文件名复制到该缓冲区。

当您运行命令时,该Unicode缓冲区将转换为当前语言环境L2,并通过馈入程序argv,并且程序可以将具有当前语言环境的argv解码为Unicode以显示。

的Linux

在Linux上,一切都不同,我对正在发生的事情感到困惑。Linux将文件名存储为 字节字符串
,而不是Unicode。因此,如果您N在区域设置L1中创建名称为文件的文件,则该文件N作为字节字符串存储在磁盘上。

当我稍后运行终端并尝试用Tab键完成名称时,我不确定会发生什么。在我看来,命令行被构造为字节缓冲区,而文件名 作为字节字符串
被串联到该缓冲区上。我假设当您键入标准字符时,它会即时编码为附加到该缓冲区的字节。

当您运行程序时,我认为缓冲区直接发送到argv。现在,有什么编码argv?在语言环境L2中,您在命令行中键入的任何字符看起来都将采用L2编码,但是
文件名将采用L1编码 。因此argv包含两种编码的混合!

如果有人能让我知道这里发生了什么,我真的很喜欢。我目前所拥有的只是半猜测和猜测,而且并不能真正融合在一起。我真正想要成为现实的是要argv在当前代码页(Windows)或当前语言环境(Linux
/ OS X)中进行编码,但事实并非如此……

附加功能

这是一个简单的候选程序P,它使您可以自己观察编码:

#include <stdio.h>

int main(int argc, char **argv)
{
    if (argc < 2) {
        printf("Not enough arguments\n");
        return 1;
    }

    int len = 0;
    for (char *c = argv[1]; *c; c++, len++) {
        printf("%d ", (int)(*c));
    }

    printf("\nLength: %d\n", len);

    return 0;
}

您可以locale -a用来查看可用的语言环境,也可以export LC_ALL=my_encoding用来更改您的语言环境。


阅读 362

收藏
2020-06-03

共1个答案

一尘不染

感谢大家的回应。我已经了解了很多有关此问题的知识,并发现了以下解决了我问题的方法:

  1. 如前所述,在Windows上,argv使用当前代码页进行编码。但是,您可以使用GetCommandLineW将命令行检索为UTF-16。不建议在支持unicode的现代Windows应用程序中使用argv,因为不建议使用代码页。

  2. 在Unixes上,argv没有固定的编码:

a)通过制表符补全/通配符插入的文件名将在argv 逐字 出现,恰好是它们在磁盘上被命名的字节序列。即使这些字节序列在当前语言环境中没有意义,也是这样。

b)用户使用其IME直接输入的输入将以区域设置编码在argv中出现。(Ubuntu似乎使用LOCALE来决定如何对IME输入进行编码,而OS
X使用Terminal.app编码首选项。)

对于想要将命令行参数视为字符串的语言,例如Python,Haskell或Java,这很烦人。他们需要决定如何解码argv为内部使用的任何编码String(对于这些语言,编码为UTF-16)。但是,如果他们只是使用语言环境编码来执行此解码,则输入中的有效文件名可能无法解码,从而导致异常。

Python
3采用的解决此问题的方法是代理字节编码方案(http://www.python.org/dev/peps/pep-0383/),该方案将argv中任何不可解码的字节表示为特殊的Unicode代码点。当该代码点被解码回字节流时,它再次变成原始字节。这允许通过本地Python字符串类型将来自argv的在当前编码中无效的数据(即,以当前语言环境以外的名称命名的文件名)通过本机Python字符串类型往返,并返回到字节,而不会丢失信息。

如您所见,情况非常混乱:-)

2020-06-03