在Perl中打印到文件与打印到Shell的区别

7

我正在编写一个Perl程序,将我的本地语言ASCII字符转换为Unicode字符(泰米尔语)。

这是我的程序:

#!/bin/perl
use strict;
use warnings;

use open ':std';
use open ':encoding(UTF-8)';

use Encode qw( encode decode );
use Data::Dump qw(dump);
use Getopt::Long qw(GetOptions);

Getopt::Long::Configure qw(gnu_getopt);

my $font;
my %map;
GetOptions(
    'font|f=s' => \$font,
    'help|h'   => \&usage,
) or die "Try $0 -h for help";

print "Do you want to map $font? (y/n)";
chomp( my $answer = lc <STDIN> );

$font = lc( $font );
$font =~ s/ /_/;
$font =~ s/(.*?)\.ttf/$1/;

if ( $answer eq "y" ) {
    map_font();
}
else {
    restore_map();
}

foreach ( @ARGV ) {

    my $modfile = "$_";

    $modfile =~ s/.*\/(.*)/uni$1/;

    process_file( $_, $modfile );
}

sub process_file {

    my @options = @_;

    open my $source, '<', "$options[0]";
    my $result = $options[1];
    my $test   = "./text";
    my $missingchar = join( "|", map( quotemeta, sort { length $b <=> length $a } keys %map ) );

    while ( <$source> ) {
        $/ = undef;
        s/h;/u;/g;       #Might need change based on the tamil font
        s/N(.)/$1N/g;    #Might need change based on the tamil font
        s/n(.)/$1n/g;    #Might need change based on the font
        s/($missingchar)/$map{$1}/g;

        print "$_";

        open my $final, '>:utf8', "$result";
        print $final "$_";
        close $final;
    }
}

sub map_font {

    my @oddhexes = qw/0B95 0B99 0B9A 0B9E 0B9F 0BA3 0BA4 0BA8 0BAA 0BAE 0BAF 0BB0 0BB2 0BB5 0BB3 0BB4 0BB1 0BA9/;
    my @missingletters = qw/0BC1 0BC2/;
    my @rest = qw/0B85 0B86 0B87 0B88 0B89 0B8A 0B8E 0B8F 0B90 0B92 0B93 0B83  0BBE  0BBF  0BC0  0BC6  0BC7  0BC8  0BCD  0B9C  0BB7  0BB8  0BB9 0BCB 0BCA 0BCC/;

    foreach ( @oddhexes ) {

        my $oddhex = $_;

        $_ = encode( 'utf8', chr( hex( $_ ) ) );
        print "Press the key for $_   :";
        chomp( my $bole = <STDIN> );
        if ( $bole eq "" ) {
            next;
        }

        $map{$bole} = $_;

        foreach ( @missingletters ) {

            my $oddchar = encode( 'utf8', chr( hex( $oddhex ) ) . chr( hex( $_ ) ) );

            print "Press the key for $oddchar   :";
            chomp( my $missingchar = <STDIN> );
            if ( $missingchar eq "" ) {
                next
            }

            $map{$missingchar} = $oddchar;
        }

    }

    foreach ( @rest ) {

        $_ = encode( 'utf8', chr( hex( $_ ) ) );

        print "Press the key for $_   :";
        chomp( my $misc = <STDIN> );
        if ( $misc eq "" ) {
            next
        }

        $map{$misc} = $_;
    }

    open my $OUTPUT, '>', $font || die "can't open file";
    print $OUTPUT dump( \%map );
    close $OUTPUT;
}

sub restore_map {

    open my $in, '<', "$font" || die "can't open file: $!";

    {
        local $/;
        %map = %{ eval <$in> };
    }

    close $in;
}

sub usage {
    print "\nUsage: $0 [options] {file1.txt file2.txt..} \neg: $0 -f TamilBible.ttf chapter.txt\n\nOptions:\n  -f --font - used to pass font name\n  -h --help - Prints help\n\nManual mapping of font is essential for using this program\n";
    exit;
}

在子程序process_file中,print "$_";的输出在终端中显示正确的泰米尔Unicode字符,如此处所示。
然而,$final的文件句柄的输出结果则非常不同。 %map的内容可以在这里查看。
为什么会产生不同的输出结果?
我该如何更正这种情况?
我已经看到了这个问题,但这并不相同。在我的情况下,终端正确地显示了结果,而文件处理器的输出却不同。

你应该考虑使用语句修饰符,例如 next if if $misc eq "",而不是 if ( $misc eq "" ) { next } - Borodin
@Borodin 谢谢你的提示。我会修改代码。 - One Face
ASCII并不是你想象中的意思。 - Sinan Ünür
我知道,我对编码不是很了解。我只想将任何类型的编码转换为UNICODE。@SinanÜnür - One Face
1个回答

9

你的开放性陈述

open my $final, '>:utf8', "$result";

将文件句柄设置为期望字符,并在输出时编码为UTF-8序列。但是,您正在发送预编码的字节序列从%map哈希中,这会导致这些字节被视为字符并由Perl IO再次编码。

相比之下,您的终端设置为期望UTF-8编码的数据,但是STDOUT没有设置任何编码(use open ':std'本身没有效果,请参见下文),因此它会无修改地传递您的UTF-8编码字节,这恰好是终端所期望的。

顺便说一下,您已经使用:encoding(UTF-8)为输入和输出流设置了默认的打开模式。

use open ':encoding(UTF-8)'

但是在你调用 open 的时候,你已经覆盖了它。 :utf8 模式只进行了非常基本的宽字符到字节序列的转换,而 :encoding(UTF-8) 则更加有用,因为它检查每个要打印的字符是否是有效的 Unicode 值。很有可能它会发现这样的错误,最好允许默认值并仅编写以下内容:

open my $final, '>', $result;

为了让程序更加规范整洁,你的程序应该使用字符,并且文件句柄在输出这些字符时应该设置为UTF-8编码。你可以通过添加以下内容来将UTF-8设置为所有新打开的文件句柄、STDIN和STDOUT的默认编码:
use open qw/ :std :encoding(utf-8) /;

:encoding(utf-8)放在程序顶部比:utf8更好,同时删除所有对encode的调用。你的做法几乎正确,但是:std:encoding(utf-8)需要在同一个use语句中。

此外,您还应该添加:

use utf8;

在程序本身中使用UTF-8字符,需要将其置于顶部。

此外,您还有一些偶发错误。例如:

  • In the statement

    open my $in, '<', "$font" || die "can't open file: $!";
    

    it is almost always wrong to quote a single scalar variable like $font unless it happens to be an object and you want to invoke the stringification method

    You need or instead of ||, otherwise you're just testing the truth of $font

    If I asked you what a variable called $in might contain I think you'd be hesitant; $in_fh is better and is a common idiom

    It's always nice to put the name of the file into the die string as well as the reason from $!

    Taking all of those into account makes your statement look like this

    open my $in_fh, '<', $font or die qq{Unable to open "$font" for input: $!};
    
  • You should be consistent between upper and lower case scalar variables, and lower case is the correct choice. So

    open my $OUTPUT, '>', $font || die "can't open file";
    

    should be something like

    open my $out_fh, '>', $font or die qq{Unable to open "$font" for output: $!};
    
  • The line

    $/ = undef;
    

    should be local $/ as you have used elsewhere, otherwise you are permanently modifying the input record separator for the rest of your program and modules. It also appears after the first read from the file handle, so your program will read and process one line, and then the whole of the rest of the file in the next iteration of the while loop


非常感谢您!您还指出了我想知道的其他事情。 - One Face
1
@OneFace:太好了。你成功地让它在没有任何encode调用的情况下工作了吗? - Borodin
我正在重新检查代码。由于使用“:std :encoding(utf-8)”时字符值非常不同,我必须重新映射字体。我仍在扩展程序并添加各种选项。一旦我重新映射完成,我会发布结果。现在是睡觉的时间了。再次感谢您的极其宝贵的帮助! - One Face
我能够在终端正确显示字符,以获得字体映射。因此编码按您建议的方式工作。 - One Face
@OneFace:太棒了。晚安斯里兰卡(?) - Borodin

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接