答案:NodeJS 总是将 args 编码为 UTF-8。
我编写了一个简单的 C++ 应用程序,它显示了传递到其 argv 的字节的原始真相:
#include <stdio.h>
int main(int argc, char *argv[])
{
printf("argc=%u\n", argc);
for (int i = 0; i < argc; i++)
{
printf("%u:\"", i);
for (char *c = argv[i]; *c != 0; c++)
{
if (*c >= 32 && *c < 127)
printf("%c", *c);
else
{
unsigned char d = *(unsigned char *)c;
unsigned int e = d;
printf("\\x%02X", e);
}
}
printf("\"\n");
}
return 0;
}
在我的 NodeJS 应用程序中,我得到了一些我确信知道它们来自哪里的字符串:
const a = Buffer.from([65]).toString("utf8");
const pound = Buffer.from([0xc2, 0xa3]).toString("utf8");
const skull = Buffer.from([0xe2, 0x98, 0xa0]).toString("utf8");
const pound2 = Buffer.from([0xa3]).toString("latin1");
toString 的参数表示缓冲区中的原始字节应该被理解为缓冲区是 UTF-8(或最后一种情况下的 latin1)。结果是我有四个字符串,我明确知道它们的内容是正确的。
(我知道 Javascript VM 通常将其字符串存储为 UTF16?在我的实验中 pound 和 pound2 的行为相同的事实证明了字符串的出处并不重要。)
最后我用这些字符串调用了 execFile:
child_process.execFileAsync("argcheck",[a,pound,pound2,skull],{encoding:"utf8"});
child_process.execFileAsync("argcheck",[a,pound,pound2,skull],{encoding:"latin1"});
在这两种情况下,nodejs 传递给 argv 的原始字节都是字符串 a,pound,pound2,skull 的 UTF-8 编码。
那么我们如何从 nodejs 传递 latin1 参数呢?
上面的解释表明 nodejs 不可能将 127..255 范围内的任何 latin1 字符传递给 child_process.spawn/execFile。但是有一个涉及 child_process.exec 的逃生舱:
- 示例:此字符串“A £ ☠”
- 在 Javascript 的 UTF16 内部存储为“\u0041 \u00A3 \u2620”
- 以 UTF-8 编码为“\x41 \xC2\xA3 \xE2\x98\xA0”
- 在 latin1 中编码为“\x41 \xA3 ?” (骷髅和交叉骨在拉丁语中无法表达)
- Unicode 字符 0-127 与 latin1 相同,编码为 utf-8 与 latin1 相同
- Unicode 字符 128-255 与 latin1 相同,但编码不同
- latin1/ 中不存在 256+ 的 Unicode 字符。
// this would encode them as utf8, which is wrong:
execFile("id3v2", ["--comment", "A £ ☠", "x.mp3"]);
// instead we'll use shell printf to bypass nodejs's wrongful encoding:
exec("id3v2 --comment \"`printf "A \xA3 ?"`\" x.mp3");
这是一种将“A £ ☠”之类的字符串转换为“A \xA3 ?”之类的字符串的便捷方法,准备传递给child_process.exec:
const comment2 = [...comment]
.map(c =>
c <= "\u007F" ? c : c <= "\u00FF"
? `\\x${("000" + c.charCodeAt(0).toString(16)).substr(-2)}` : "?")
)
.join("");
const cmd = `id3v2 --comment \"\`printf \"${comment2}\"\`\" \"${fn}\"`;
child_process.exec(cmd, (e, stdout, stderr) => { ... });