argv的编码是什么?

时间:2011-03-23 16:45:09

标签: c linux unicode encoding

我不清楚在C argv中使用了什么编码。特别是,我对以下场景感兴趣:

  • 用户使用区域设置L1创建名称为N的文件包含非ASCII字符的文件
  • 稍后,用户使用区域设置L2在命令行上标记完成该文件的名称,该文件作为命令行参数提供给程序P

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

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

这是我认为到目前为止所发生的事情(很久,我可能错了,需要纠正):

文件名以某种Unicode格式存储在磁盘上。因此,Windows取名为N,从L1(当前代码页)转换为Unicode版N,我们将调用N1,并将N1存储在磁盘上。< / p>

假设发生的事情是,当稍后完成制表符时,名称N1将转换为区域设置L2(新的当前代码页)以供显示。幸运的是,这将产生原始名称N - 但如果N包含在L2中无法代表的字符,则不会这样。我们将新名称称为N2

当用户实际按Enter键以使用该参数运行P时,名称N2将转换回Unicode,再次产生N1。此N1现已通过GetCommandLineW / wmain / tmain以UCS2格式提供给该计划,但GetCommandLine / main的用户将会看到当前语言环境(代码页)中的名称N2

OS X

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

使用Unicode终端,我认为终端在Unicode缓冲区中构建命令行会发生什么。因此,当选项卡完成时,它会将文件名作为Unicode文件名复制到该缓冲区。

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

的Linux

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

当我稍后运行终端并尝试选项卡完成名称时,我不确定会发生什么。在我看来,命令行被构造为字节缓冲区,文件作为字节串的名称只是连接到该缓冲区。我假设当你键入一个标准字符时,它会被动态编码为附加到该缓冲区的字节。

运行程序时,我认为缓冲区直接发送到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更改您的区域设置。

4 个答案:

答案 0 :(得分:20)

感谢大家的回复。我已经对这个问题了解了很多,并且发现了以下解决了我问题的事情:

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

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

    a)tab-completion / globbing插入的文件名将在argv verbatim 中出现,就像它们在磁盘上命名的字节序列一样。即使这些字节序列在当前语言环境中没有意义,也是如此。

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

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

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

    正如你所看到的,情况非常混乱: - )

答案 1 :(得分:6)

我现在只能谈论Windows。在Windows上,代码页仅适用于遗留应用程序,不供系统或现代应用程序使用。 Windows使用UTF-16(并且已经使用了很长时间)用于所有内容:文本显示,文件名,终端,系统API。 UTF-16和遗留代码页之间的转换仅在最高级别执行,直接在系统和应用程序之间的接口上执行(从技术上讲,旧的API函数实现两次,一个函数FunctionW执行实际工作并期望UTF-16字符串和一个兼容性函数FunctionA,它只是将输入字符串从当前(线程)代码页转换为UTF-16,调用FunctionW,并转换回结果) 。 Tab-completion应该总是产生UTF-16字符串(当使用TrueType字体时肯定会产生),因为控制台也只使用UTF-16。制表符完成的UTF-16文件名将移交给应用程序。如果现在该应用程序是遗留应用程序(即,它使用main而不是wmain / GetCommandLineW等),那么Microsoft C运行时(可能)使用GetCommandLineA来让系统转换命令行。所以基本上我认为你所说的Windows是正确的(只有在完成制表符时可能没有涉及转换):argv数组将始终包含当前代码页中的参数应用程序,因为原始程序所使用的代码页(L1)的信息在中间UTF-16阶段已经不可逆转地丢失。

结论一如既往地在Windows上:避免遗留代码页;尽可能使用UTF-16 API。如果您必须使用main而不是wmain(例如,与平台无关),请使用GetCommandLineW代替argv数组。

答案 2 :(得分:2)

测试应用的输出需要进行一些修改才有意义, 你需要十六进制代码,你需要摆脱负值。 或者你不能打印像UTF-8特殊字符这样的东西,所以你可以阅读它们。

首先修改了SW:

#include <stdio.h>

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

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

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

    return 0;
}

然后在我使用UTF-8的Ubuntu盒子上,我得到了这个输出。

$> gcc -std=c99 argc.c -o argc
$> ./argc 1ü
31 c3 bc 
Length: 3

在这里你可以看到,在我的情况下,ü被编码为2个字符, 并且1是单个字符。 或多或少完全符合您对UTF-8编码的期望。

这实际上与env LANG varible中的内容相匹配。

$> env | grep LANG
LANG=en_US.utf8

希望这能澄清linux案例。

/祝你好运

答案 3 :(得分:1)

是的,用户在Unix上混合语言环境时必须要小心。显示和更改文件名的GUI文件管理器也存在此问题。在Mac OS X上,标准的Unix编码是UTF-8。实际上,当通过Unix接口调用时,HFS +文件系统强制执行UTF-8文件名,因为它需要将其转换为UTF-16以便存储在文件系统中。