Perl + Curses:期望从 getchar() 得到一个 UTF-8 编码的多字节字符,但没有得到任何字符

Dav*_*fer 3 perl encoding locale ncurses utf-8

我正在尝试使用 Bryan Henderson 对 ncurses 库的 Perl 接口:Curses

对于一个简单的练习,我尝试获取在屏幕上输入的单个字符。这直接基于NCURSES Programming HOWTO并进行了修改。

当我调用 Perl 库的 时getchar(),我希望收到一个字符,可能是多字节的(正如库手册页的这一部分所解释的那样,它有点复杂,因为必须处理功能键和没有输入的特殊情况,但这只是通常的卷发)。

它是read1ch()下面代码中的子程序。

这适用于 ASCII 字符,但不适用于 0x7F 以上的字符。例如,当点击è(Unicode 0x00E8, UTF-8 : 0xC3, 0xA8) 时,我实际上获得了代码 0xE8 而不是 UTF-8 编码的东西。将其打印到LANG=en_GB.UTF-8无法正常工作的终端,无论如何我期待 0xC3A8。

我需要更改什么才能使其工作,即获取è正确的字符或 Perl 字符串?

剪断C代码getchar()在这里顺便说一句。也许它只是没有用C_GET_WCHset编译?如何发现?

附加物

附录 1

与设置尝试binmode使用

binmode STDERR, ':encoding(UTF-8)';
binmode STDOUT, ':encoding(UTF-8)';
Run Code Online (Sandbox Code Playgroud)

这应该解决任何编码问题,因为终端期望并发送 UTF-8,但这没有帮助。

还尝试使用 open设置流编码(不太确定此方法与上述方法之间的区别),但这也无济于事

use open qw(:std :encoding(UTF-8));
Run Code Online (Sandbox Code Playgroud)

附录 2

Perl Curses 垫片的联机帮助页说:

如果wget_wch()不可用(即 Curses 库不理解宽字符),则调用wgetch()[从curses 窗口获取 1 字节字符],但仍返回上述值。这可能是一个问题,因为对于像 UTF-8 这样的多字节字符编码,您将收到两个单字符的字符串来表示两字节字符(例如,“Ô和“¤”表示“ä”)。

这可能是这里的情况,但wget_wch()在这个系统上确实存在。

附录 3

试图查看 C 代码的作用,并fprintf直接在 的多字节处理代码中添加了一个curses/Curses-1.36/CursesFunWide.c,重新编译,并没有设法Curses.so用我自己的 via覆盖系统LD_LIBRARY_PATH(为什么不呢?为什么一切只在一半的时间内工作?),所以直接替换了系统库(拿去吧!)。

#ifdef C_GET_WCH
    wint_t wch;
    int ret = wget_wch(win, &wch);
    if (ret == OK) {
        ST(0) = sv_newmortal();
        fprintf(stderr,"Obtained win_t 0x%04lx\n", wch);
        c_wchar2sv(ST(0), wch);
        XSRETURN(1);
    } else if (ret == KEY_CODE_YES) {
        XST_mUNDEF(0);
        ST(1) = sv_newmortal();
        sv_setiv(ST(1), (IV)wch);
        XSRETURN(2);
    } else {
        XSRETURN_UNDEF;
    }
#else
Run Code Online (Sandbox Code Playgroud)

这只是一个肥胖的 NOPE,当按下ü一个看到:

Obtained win_t 0x00fc
Run Code Online (Sandbox Code Playgroud)

所以正确的代码是 run,但数据是ISO-8859-1,而不是 UTF-8 。所以它的wget_wch行为很糟糕。所以这是一个curses配置问题。呵呵。

附录 4

让我震惊的是,可能ncurses是假设默认语言环境,即C. 为了使其ncurses与宽字符一起工作,必须“初始化语言环境”,这可能意味着将状态从“未设置”(从而ncurses回退到C)移动到“设置为系统指示的内容”(这应该是在LANG环境变量)。手册页ncurses说:

库使用调用程序已初始化的语言环境。这通常是通过 setlocale 完成的:

设置区域设置(LC_ALL,“”);

如果区域设置未初始化,则库假定字符可打印,如 ISO-8859-1 中所示,以与某些遗留程序一起使用。当区域设置尚未设置时,您应该初始化区域设置,而不是依赖库的特定细节。

这也不起作用,但我觉得解决方案就在这条路上。

附录 5

来自 的win_t(显然与 相同wchar_t)转换代码CursesWide.c将接收到的wint_t(此处视为wchar_twget_wch()转换为 Perl 字符串。SV是“标量值”类型。

另见:https : //perldoc.perl.org/perlguts.html

这里用两个fprintf插入看看是怎么回事:

static void
c_wchar2sv(SV *    const sv,
           wchar_t const wc) {
/*----------------------------------------------------------------------------
  Set SV to a one-character (not -byte!) Perl string holding a given wide
  character
-----------------------------------------------------------------------------*/
    if (wc <= 0xff) {
        char s[] = { wc, 0 };
        fprintf(stderr,"Not UTF-8 string: %02x %02x\n", ((int)s[0])&0xFF, ((int)s[1])&0xFF);
        sv_setpv(sv, s);
        SvPOK_on(sv);
        SvUTF8_off(sv);
    } else {
        char s[UTF8_MAXBYTES + 1] = { 0 };
        char *s_end = (char *)UVCHR_TO_UTF8((U8 *)s, wc);
        *s_end = 0;
        fprintf(stderr,"UTF-8 string: %02x %02x %02x\n", ((int)s[0])&0xFF, ((int)s[1])&0xFF, ((int)s[2])&0xFF);
        sv_setpv(sv, s);
        SvPOK_on(sv);
        SvUTF8_on(sv);
    }
}
Run Code Online (Sandbox Code Playgroud)

使用 perl-Curses 测试代码

  • 尝试使用 perl-Curses-1.36-9.fc30.x86_64
  • 尝试使用 perl-Curses-1.36-11.fc31.x86_64

如果您尝试,请按 BACKSPACE 退出循环,因为不再解释 CTRL-C。

下面的代码很多,但关键区域标有----- Testing

#ifdef C_GET_WCH
    wint_t wch;
    int ret = wget_wch(win, &wch);
    if (ret == OK) {
        ST(0) = sv_newmortal();
        fprintf(stderr,"Obtained win_t 0x%04lx\n", wch);
        c_wchar2sv(ST(0), wch);
        XSRETURN(1);
    } else if (ret == KEY_CODE_YES) {
        XST_mUNDEF(0);
        ST(1) = sv_newmortal();
        sv_setiv(ST(1), (IV)wch);
        XSRETURN(2);
    } else {
        XSRETURN_UNDEF;
    }
#else
Run Code Online (Sandbox Code Playgroud)

ike*_*ami 5

[此答案假定 libncursesw 可用并正在使用。尝试在没有宽字符支持的情况下输出“宽字符”是没有意义的:) ]


简答

getchar工作正常。它返回一串 Unicode 代码点(又名解码文本),这是理想的。

printw 已损坏,但可以通过将以下内容添加到程序中,使其接受一串 Unicode 代码点(又名解码文本):

{
   # Add wide character support to printw.
   # This only modifies the current package (main),
   # so it won't affect any code by ours.
   no warnings qw( redefine );
   sub printw { addstring(sprintf shift, @_) }
}

Run Code Online (Sandbox Code Playgroud)

有问题getchar吗?

所以你认为getchar. 让我们尝试通过检查getchar返回的内容来确认这一点。我们将通过添加以下内容来做到这一点:

printw("String received from getchar: %vX\n", $ch);
Run Code Online (Sandbox Code Playgroud)

%vX将以十六进制打印字符串的每个字符的值,并用句点连接。)

在所有三种情况下,我们都会得到一个长度正好为一个字符的字符串,并且该字符由输入的 Unicode 代码点组成。[1]这正是我们想要的。应用和移除字符编码应该在外围完成,这样程序的主要逻辑就不必担心编码,而这正在完成。

结论:没有问题getchar


有问题printw吗?

所以问题一定出在输出上。为了确认这一点,我在您的程序中添加了以下内容:

sub _d { utf8::downgrade( my $s = shift ); $s }
sub _u { utf8::upgrade(   my $s = shift ); $s }

for (
   [ "7-bit, UTF8=0" => _d(chr(0x65)) ],   # Expect e
   [ "7-bit, UTF8=1" => _u(chr(0x65)) ],   # Expect e
   [ "8-bit, UTF8=0" => _d(chr(0xE9)) ],   # Expect é
   [ "8-bit, UTF8=1" => _u(chr(0xE9)) ],   # Expect é
   [ "9-bit, UTF8=1" => chr(0x113)    ],   # Expect ?
) {
   my ($name, $chr) = @$_;
   printw("%s: %s\n", $name, $chr);
}
Run Code Online (Sandbox Code Playgroud)

输出:

7-bit, UTF8=0: e
7-bit, UTF8=1: e
8-bit, UTF8=0:
8-bit, UTF8=1: é
9-bit, UTF8=1:  S
Run Code Online (Sandbox Code Playgroud)

综上所述,我们观察到:

  • 我们看到的结果之间的差异的_d(chr(0xE9))_u(chr(0xE9)),即使标量都包含相同的字符串(_d(chr(0xE9)) eq _u(chr(0xE9))是真的)。因此,此功能会受到 Unicode 错误的影响。
  • 根据 8 位测试,它似乎接受 Unicode 代码点(解码文本)而不是 UTF-8。这是理想的。
  • 根据 9 位测试,它似乎不接受 Unicode 代码点。随后的测试表明它不接受两者的 UTF-8 编码chr(0x113)

结论:存在重大问题printw


解决问题 printw

解决 Unicode 错误很容易,但缺乏对 0​​xFF 以上字符的支持是一个障碍。让我们深入研究代码。

好的,我们不必费力寻找问题。我们看到它printw是根据 , 定义的addstr,并且addstr早于宽字符支持。 addstring是宽字符支持对方,所以我们要尽量printw使用addstring替代addstr

{
   # Add wide character support to printw.
   # This only modifies the current package (main),
   # so it won't affect any code by ours.
   no warnings qw( redefine );
   sub printw { addstring(sprintf shift, @_) }
}
Run Code Online (Sandbox Code Playgroud)

输出:

7-bit, UTF8=0: e
7-bit, UTF8=1: e
8-bit, UTF8=0: é
8-bit, UTF8=1: é
9-bit, UTF8=1: ?
Run Code Online (Sandbox Code Playgroud)

答对了!

综上所述,我们观察到:

  • 我们看不到UTF8=0测试结果与其相应UTF8=1测试之间的差异。因此,此功能不受 Unicode 错误的影响。
  • 它自始至终都接受 Unicode 代码点(解码文本)字符串。值得注意的是,它不期望 UTF-8 或语言环境的编码。

这正是我们期望/渴望的。


  1. 具体来说,getchar不会像您认为的那样返回输入的 iso-8859-1 编码。这种混淆是可以理解的,因为 Unicode 是 iso-8859-1 的扩展。