【问题标题】:What is the encoding of argv?argv的编码是什么?
【发布时间】:2011-07-21 11:48:51
【问题描述】:

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

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

P 在命令行中看到的字节序列是什么?

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

这是我认为到目前为止的情况(很长,我可能错了,需要纠正):

窗口

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

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

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

操作系统

据我所知,磁盘存储的故事是一样的。 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 更改您的语言环境。

【问题讨论】:

  • 在带有 Visual C 标准库的 Windows 中,您可以实现 wmain() 并获得 WCHAR * wz_argv[]。如果您选择在 Windows 上实现自己的 C 标准库,您将从 WinMain 开始,它采用整个原始命令行的 Unicode 字符串。在 Windows 上,您肯定正在运行 cmd.exe,并且所有管道都是 Unicode。至于 Unix 衍生产品,相对于您的问题,最关键的决策代码是 shell。例如,如果您正在运行 bash,那么该版本 bash 的行为就是您要询问的内容。
  • 您只能在 Windows 上的命令行应用程序中使用 wchar argv。在 gui 构建中,您必须单独使用 GetCommandLine 来获取宽字符
  • @Martin - 我没有意识到 WinMain 也是从 MSVC 库中调用的,也不是 ANSI。原来,可以选择使用 wWinMain 来获取 Unicode。
  • stackoverflow.com/questions/4101864/…其中一些是Qt特有的,但答案中有很多有用的信息
  • 感谢您迄今为止的回复。似乎很明显,使用 Windows 和 wmain(我在回答中提到过)你会得到 UCS-2 编码的 Unicode 命令行参数。我想使用带有 char** argv 的 Windows 主程序,您会在当前代码页中获得编码的参数,但这是我不清楚的事情之一。

标签: c linux unicode encoding


【解决方案1】:

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

  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 编码首选项。)

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

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

如您所见,情况相当混乱 :-)

【讨论】:

  • 由于 UTF-16 固有的限制,往返不是问题吗?处理 UTF-8 或 UTF-32 的东西不就没有这样的麻烦了吗?
  • 不确定你的意思:如果 Python 在内部使用 UTF-8 字符串,它仍然必须使用转义方案来表示命令行上无法使用语言环境编码解码的任何字符.
  • "在 Windows 上,argv 使用当前代码页进行编码。" 更准确地说,是 ANSI 代码页,而不是 OEM 代码页。如果您开发控制台应用程序,则必须小心不要混淆两者。
【解决方案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 变量中的内容相匹配。

$> env | grep LANG
LANG=en_US.utf8

希望这能稍微澄清一下 linux 的情况。

/祝你好运

【讨论】:

    【解决方案3】:

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

    【讨论】:

      【解决方案4】:

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

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

      【讨论】:

      • 谢谢,这确实证实了我在 Windows 上的想法。现在我希望其他人可以澄清 OS X / Linux 的情况......
      猜你喜欢
      • 1970-01-01
      • 2011-10-24
      • 1970-01-01
      • 1970-01-01
      • 2022-06-20
      • 1970-01-01
      • 2014-12-30
      • 2011-03-02
      • 2011-03-22
      相关资源
      最近更新 更多