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

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

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

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

以下是我目前的 idea (很长,我可能错了,需要纠正):

Windows

文件名以某种Unicode格式存储在磁盘上.所以Windows使用名称N,将L1(当前代码页)转换为Unicode版本的N,我们称之为N1,并将N1存储在磁盘上.

然后,当tab稍后完成时,名称N1被转换为locale L2(新的当前代码页)以供显示.幸运的是,这将产生原来的名字N——但如果N包含在L2中不可表示的字符,这将不是真的.我们把这个新名字叫做N2.

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

OS X

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

对于Unicode终端,I think发生的事情是,终端在Unicode缓冲区中构建命令行.因此,当您使用tab complete时,它会将文件名作为Unicode文件名复制到该缓冲区.

运行该命令时,Unicode缓冲区将转换为当前语言环境L2,并通过argv馈送到程序,程序可以将当前语言环境下的argv解码为Unicode进行显示.

Linux

在Linux上,一切都不一样,我对正在发生的事情感到非常困惑.Linux将文件名存储为byte strings,而不是Unicode.所以,如果在语言环境L1中创建一个名为N的文件,那么N作为字节字符串存储在磁盘上.

当我稍后运行终端并try 完成名称时,我不确定会发生什么.在我看来,命令行被构造为字节缓冲区,而文件as a byte string的名称只是连接到该缓冲区上.我假设,当您键入一个标准字符时,它会被动态编码为附加到该缓冲区的字节.

当你运行一个程序时,我认为缓冲区会直接发送到argv.现在,argv有什么编码?看起来,在语言环境L2中,您在命令行中键入的任何字符都将使用L2编码,但the file name will be in the L1 encoding.所以argv包含两种编码的混合!

Question

如果有人能告诉我这里发生了什么,我真的很高兴.目前我所能做的只是猜测和猜测,而这两者并不完全吻合.我真正希望的是,在当前代码页(Windows)或当前语言环境(Linux/OS X)中对argv进行编码,但情况似乎并非如此...

Extras

下面是一个简单的候选程序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更改区域设置.

推荐答案

谢谢大家的回复.关于这个问题,我学到了很多,并发现以下几点解决了我的问题:

  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).但是,如果他们只是使用区域设置编码进行解码,那么输入中的有效文件名可能无法解码,从而导致异常.

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

正如你所看到的,情况相当混乱:-)

Linux相关问答推荐

使用信号处理程序实现Hibernate 功能

AWK打印到文件正在追加,而不是覆盖

Docker 守护进程安装在 ubuntu jenkins docker 容器代理权限被拒绝

sed + 从没有额外空格的文本中删除单词

从另一个文件中的大文件中查找行的最快方法

awk 不打印所需的 df 输出

sed 命令在 gitlab runner 上无法正确执行

如何忽略 diff 命令中的一些差异?

如何在 shell 脚本中只读取一个字符

XML 编辑/查看软件

用于 Linux 的 Less 编译器

如何在 Linux 中查找所有以 .rb 结尾的文件?

php.ini 更改,但在 Ubuntu 上无效

如何让 GNU 屏幕读取 .bash_profile/.bash_rc 更改?

如何使用 bash 在文件中间添加一行文本?

在 bash 中将输出作为 cp 的参数传递

如何将文件夹中的文件列表发送到Linux中的txt文件

如何在 Linux 中查看日志(log)文件并在查看时应用自定义过滤器?

Docker:您是否try 连接到没有 TLS 的启用 TLS 的守护进程?

在linux中将制表符分隔的文件转换为csv的最快方法