【问题标题】:printf aligning problem with degree (°) character带有度数(°)字符的printf对齐问题
【发布时间】:2019-11-22 16:00:09
【问题描述】:

我有一个类似How can I properly align UTF-8 strings with Perl's printf?的问题:

我的 (Linux) 系统的区域设置默认为 LC_CTYPE=de_DE.UTF-8,我编写了一个 Perl 程序(使用 perl-5.26.1),它“不”使用 Unicode 字符,但有些来自 ISO Latin-1 字符集(即° 例如)。 因此,我没有在我的 Perl 脚本中激活任何 Unicode 或区域设置功能。

“Everything”似乎在一个例外情况下工作正常:我使用%-10sprintf 格式来对齐字符串,但这不能按预期工作。

在调试器中播放我发现了这种行为:

  DB<1> $s='X°X'

  DB<2> printf("_%3s_\n", $s)
_X°X_

目前看起来还不错...

  DB<3> printf("_%4s_\n", $s)
_X°X_

哎呀;不应该是"_ X°X_"吗?

  DB<4> printf("_%5s_\n", $s)
_ X°X_

减一?

  DB<5> x length($s)
0  4

不应该是3吗?

  DB<8> x ord($s[1])
0  0
  DB<9> x $s
0  'X°X'
  DB<10>

° 不应该被编码为一个字节吗?我认为 UTF-8 将未修改的 Latin-1 范围映射到 Unicode。

所以问题可能是:

  1. 发生了什么事?

  2. 这是 Perl 错误吗?

  3. 如果不是,如何修复格式和字符串长度?

【问题讨论】:

  • @Keith Thompson:我之前读过,但实际上en.wikipedia.org/wiki/UTF-8#Codepage_layout 暗示这不是真的:拉丁语 1 的 部分 被逐字映射为 UTF-8。特别是“U+00B0°度符号”。
  • 维基百科页面指出 $B0 在 utf-8 编码方案的连续字节范围内,由此可知 Unicode 代码点 U+00B0 未按 1:1 映射。分析 utf8 编码的规范表明,如果设置了 utf8 代码的第一个八位字节的第 7 位,则该字节包含指示代码长度的位,因此该范围内的任何代码点都不可能进行 1:1 映射U+0080 到 U+00FF。
  • @collapsar:好的,请原谅我对 UTF-8 编码的误解。
  • 所以°的Unicode字符码还是$B0,但实际编码是$C2 $B0?
  • @U.Windl:Unicode 对字符 0..255 使用相同的数值。 UTF-8 将 0..127 (ASCII) 范围内的每个字符编码为一个字节,并将 128..255 范围内的每个字符(ASCII 之外,Latin-1 之内)编码为两个字节。最多 2047 个字符也是编码为两个字节。请记住,UTF-8 不是 Unicode;它是 Unicode 的几种编码之一。

标签: perl utf-8 character-encoding printf


【解决方案1】:

UTF-8 仅将 ASCII 范围 (0..127) 映射到 1 个字节。 Latin-1 字符在 0..255 范围内; UTF-8 不能将它们全部映射到一个字节。如果是这样,就没有其他任何映射了。

从 0 到 127 的字符被编码为 1 个字节。
从 128 到 2047 的字符被编码为 2 个字节。
以此类推。

https://en.wikipedia.org/wiki/UTF-8

您的 Perl 脚本中需要 use utf8;binmode STDOUT, ':encoding(UTF-8)';(为了保持一致性,我对 STDINSTDERR 做了同样的操作):

#!/usr/bin/perl

use strict;
use warnings;
use utf8;

BEGIN {
    binmode STDIN,  ':encoding(UTF-8)';
    binmode STDOUT, ':encoding(UTF-8)';
    binmode STDERR, ':encoding(UTF-8)';
}

printf "|%-10s|\n", "x";
printf "|%-10s|\n", "°";

输出正确对齐:

|x         |
|°         |

如果我注释掉 use utf8;binmode STDOUT, ':encoding(UTF-8)';,则输出未对齐和/或度数字符显示不正确。

引用perldoc utf8utf8 模块的文档):

use utf8”编译指示告诉 Perl 解析器允许 UTF-8 在 当前词法范围内的程序文本。

(这需要配置为显示 UTF-8 的输出设备或终端模拟器。)

【讨论】:

  • 当我use utf8时发生奇怪的事情:在源°被编码为\xb0(根据Emacs),字符输出也是\xb0,但binmode STDOUT, 'utf8' ° 的输出是 \xc2\xb0。但是,当我将输出加载到 Emacs 中时,它声称字符编码为 Char: ° (176, #o260, #xb0, file ...)...
  • @U.Windl:然后你的源文件被编码为 Latin-1 或类似的东西。 DEGREE SIGN 字符(代码点 0xb0)的 UTF-8 编码为 (0xc2, 0xb0)。最好对源文件始终使用 UTF-8。但是,如果您有很多 Latin-1 编码的源文件,那么翻译它们可能不是一件容易的事。 (如果您想这样做,请参阅iconv 命令。)如果您想将源文件保留为 Latin-1,这可能会更复杂。
  • @ikegami:所以man perluniintro还是推荐binmode(STDOUT, ":utf8")的时候错了?
  • 请注意,在 Unicode 中,单个字符在打印到终端(或类似的面向列的输出格式)时可以具有 0、1、2 或不明确的宽度。 Perl 的lengthsprintf 不会在所有情况下都给出正确的结果。请改用columns in Unicode::GCString
【解决方案2】:

Perl 代码必须使用 ASCII(no utf8;,默认)或 UTF-8(use utf8;)进行编码。

° 不在 ASCII 字符集中,而且您显然也没有 use utf8;,因此您的程序不可能包含您声称的 °

首先,使用 UTF-8 对程序进行编码(如果还没有的话)并告诉 Perl 你的程序是使用 UTF-8 编码的,方法是添加

use utf8;   # The source code is encoded using UTF-8.

其次,您显然也没有告诉 Perl 对您打印的内容进行编码。通过添加来解决这个问题

use open ':std', ':encoding(UTF-8)';   # The terminal provides/expects UTF-8.

后者为在 pragma 范围内打开的文件设置默认编码。如果您想避免这种情况,您可以使用以下代码:

BEGIN {   # The terminal provides/expects UTF-8.
   binmode(STDIN,  ':encoding(UTF-8)');
   binmode(STDOUT, ':encoding(UTF-8)');
   binmode(STDERR, ':encoding(UTF-8)');
}

【讨论】:

  • 我不明白ASCII上的说法:我一直在用ISO Latin1字符集编写Perl代码,过去甚至处理过这样的输入和输出数据都没有问题。因此,除非 Perl 最近对此有所更改,否则您的“US-ASCII 或 UTF-8”声明似乎不正确。
  • Perl 不知道也从来不知道有关 iso-latin-1 的任何事情。我只能推测,在您过去的经验中,错误组合大多相互抵消(as explained here),但您终于遇到了一种情况,情况并非如此。
  • 也许就我的处理水平而言,对于 Latin1 处理来说,perl 字符串是“八位干净”的就足够了(只需写下你未修改的内容)。
  • 假设源代码、输入数据和输出数据都使用相同的编码,即$LANG$LC_CTYPE,是否有简化?
  • 我想你要的是use open ':std', ':encoding(locale)';?源仍然需要是 ASCII (no utf8;) 或 UTF-8 (use utf8;)。另外,see this
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2021-07-16
  • 1970-01-01
  • 2020-12-20
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多