【问题标题】:Special characters are not displayed correctly in the Linux TerminalLinux 终端中的特殊字符无法正确显示
【发布时间】:2014-08-02 16:46:15
【问题描述】:

我有一个以 UTF-8 编码的文件,如以下命令所示: file -i D.txt D.txt: text/plain; charset=utf-8 我只想一个接一个地显示每个字符,所以我这样做了:

FILE * F_entree = fopen("D.txt", "r");
if (! F_entree) usage("impossible d'ouvrir le fichier d'entrée");

char ligne[TAILLE_MAX];
while (fgets(ligne, TAILLE_MAX, F_entree))
{
    string mot = strtok(strdup(ligne), "\t");

    while (*mot++){printf("%c \n", *mot) ;}     
}

但特殊字符在终端(在 Ubuntu 12 上)中没有很好地显示(而是显示 <?>)。我觉得问题是%c里只能存ASCII码,但是怎么显示那些特殊字符呢?

将这些字符保存在内存中的好方法是什么(以实现树索引)? (我知道最后一个问题不清楚,请随时要求澄清。)

【问题讨论】:

  • 您的while 看起来应该是if(mot) for(; *mot; ++*mot)(编辑:我认为您不想增加mot)。
  • 是的,确实如此,但我是为了示例而写的,这不是重点。
  • 不!请参阅下面接受的详细答案;)
  • 没错。 @mafso:您不能只打印多字节 UTF8 字符的 part

标签: c character-encoding


【解决方案1】:

它不起作用,因为您的代码将多字节字符分成单独的字符。由于您的控制台期望是一个有效的多字节代码,在看到第一个代码之后,它没有接收正确的代码,你得到你的<?>——免费翻译,“啊?”。它没有收到正确的代码,因为您在其中填充了空格和换行符。

只有在您发送正确的代码并以正确的顺序发送时,您的控制台才能正确解释 UTF8 字符。算法是:

  1. 下一个字符是 UTF-8 序列的起始码吗?如果没有,请打印并继续。
  2. 如果是,则打印并打印此字符的所有“下一个”代码。实际编码见Wikipedia on UTF8;我在下面的代码中使用了快捷方式。
  3. 然后才打印您的空格 (..?) 和换行符。

识别 UTF8 多字节字符的开头和长度的过程如下:

  1. “常规”(ASCII) 字符永远不会设置第 7 位。针对0x80 的测试足以将它们与UTF8 区分开来。
  2. 每个 UTF8 字符序列开始以位模式 110xxxxx1110xxxx11110xxx111110xx1111110x 之一。每个唯一的位模式都有相关的额外字节数。例如,第一个需要 一个 额外的字节。 xxx 位与来自下一个字节的位组合以形成 16 位或更长的 Unicode 字符。 (毕竟,这就是 UTF8 的意义所在。)
  3. 每个下一个字节——不管有多少! -- 具有位模式10xxxxxx。重要提示:没有之前的模式都以此代码开头!

因此,只要您看到任何 UTF8 字符,您就可以立即显示它所有“下一个”代码,只要它们以位模式@ 开头987654334@。这可以使用位掩码进行有效测试:value & 0xc0,结果应该是0x80。任何其他值都意味着它不再是“下一个”字节,所以你就完成了。

所有这些只有在您的源文件是有效的 UTF8 时才有效。如果你看到一些奇怪的输出,很可能不是。如果您需要检查输入文件的有效性,确实需要在 Wikipedia 页面中实现整个表格,并检查每个 110xxxxx 字节实际上是否后面跟着一个 10xxxxxx 字节, 等等。出现在自身上的模式10xxxxxx 表示错误。

绝对必读的是 Joel Spolsky 的 The Absolute Minimum Every Software Developer Absolutely, Positively Must Know About Unicode and Character Sets (No Excuses!)。有关更多背景信息,另请参阅UTF-8 and Unicode FAQ for Unix/Linux


我下面的代码解决了您的其他一些问题。我使用了英文变量名(参见 Meta Stackoverflow "Foreign variable names etc. in code")。在我看来strdup 是没有必要的。另外,string 是一个 C++ 表达式。

我的代码不会“修复”或处理 UTF-8 打印之外的任何内容。由于您使用了strtok,因此代码仅打印输入文件中每一行上第一个\t 制表符之前的文本。我假设你知道你在那里做什么;-)

补充:啊,忘了说 Q2,“将这些字符保存在内存中的好方法是什么”。 UTF8 旨在最大程度地兼容 C 类型的 char 字符串。您可以这样安全地存储它们。你不需要做任何特别的事情来在一个支持 UTF8 的控制台上打印它们——好吧,除非你像在这里一样做一些事情,将它们打印为单独的字符。 printf 应该适用于整个单词。

如果您需要 strcmpstrchrstrlen 的 UTF8 感知等效项,您可以编写自己的代码(请参阅上面的 Wikipedia 链接)或为自己找到一个好的预制库。 (我故意漏掉了strcpy!)

#define MAX_LINE_LENGTH 1024

int main (void)
{
    char line[MAX_LINE_LENGTH], *word;

    FILE *entry_file = fopen("D.txt", "r");

    if (!entry_file)
    {
        printf ("not possible to open entry_file\n");
        return -1;
    }

    while (fgets(line, MAX_LINE_LENGTH, entry_file))
    {
        word = strtok(line, "\t");

        while (*word)
        {
            /* print UTF8 encoded characters as a single entity */
            if (*word & 0x80)
            {
                do
                {
                    printf("%c", *word);
                    word++;
                } while ((*word & 0xc0) == 0x80);
                printf ("\n");
            } else
            {
                /* print low ASCII characters as-is */
                printf("%c \n", *word);
                word++;
            }
        }     
    }

    return 0;
}

【讨论】:

  • 感谢您的出色回答!对于字符串,我知道,我在这里没有注意到它,但我定义了typedef char * string。我知道我在用 strtok 做什么,你猜得很好 ;) 我在下一篇关于外部变量的帖子中注明 ;) 最后,我不完全理解这些行:*word & 0x80(*word & 0xc0) == 0x80,你能请给我解释一下?
  • 其实第一个我明白,就是要知道字符码是不是小于128,但是第二个我就不清楚了;)
  • 好了,添加了一些额外的信息。并且阅读必读。不太枯燥,Joel 的文章从一开始就很有趣。
  • 完美,我阅读了必读和您的编辑,我完全明白了,非常感谢这个非常好的答案!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-12-21
  • 2017-06-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-05-13
相关资源
最近更新 更多