愚蠢的数据技巧

图片来源: Rob Nguyen on Flickr
我之前写过关于 愚蠢的开放技巧 的文章,所以现在是时候介绍一些愚蠢的 DATA
技巧了。你可能已经知道,你可以在 Perl 程序中“嵌入”一个文件,然后从 DATA
文件句柄中读取它。David Farrell 在 你应该知道的 Perl 标记 中提到了这一点,他提醒了我这里将要展示的奇异之处。
在 __DATA__
行之后的所有内容都不是程序的一部分,但可以通过特殊的 DATA
文件句柄提供给程序。
#!/usr/bin/perl
print "---Outputting DATA\n", <DATA>, "---Done\n";
__DATA__
Dog
Cat
Bird
输出显示了 __DATA__
行之后的每一行。
---Outputting DATA
Dog
Cat
Bird
---Done
我通常采取相反的方式,先从数据文件开始,然后在顶部添加一个程序。
#!/usr/bin/perl
use v5.26;
use Text::CSV_XS;
my $csv = Text::CSV_XS->new;
while( my $row = $csv->getline(*DATA) ) {
say join ':', $row->@[3,7];
}
__DATA__
...many CSV lines...
这是结束,我的朋友,是 END。
你可能也知道,你可以使用 __END__
来代替。我习惯使用它,因为它是从 Perl 4 保留下来的,我第一次就是在这里学到的。
#!/usr/bin/perl
print "---Outputting DATA\n", <DATA>, "---Done\n";
__END__
Dog
Cat
Bird
你得到相同的输出。
---Outputting DATA
Dog
Cat
Bird
---Done
但现在让我们来一点技巧。在程序末尾定义一个包。这仍然使用 __END__
。
#!/usr/bin/perl
print "---Outputting DATA\n", <DATA>, "---Done\n";
package not::main;
__END__
Dog
Cat
Bird
再次,它输出的东西和之前一样。这里没有什么令人惊讶的,但悬念正在积累。
---Outputting DATA
Dog
Cat
Bird
---Done
将那个 __END__
改为 __DATA__
再试一次。
#!/usr/bin/perl
print "---Outputting DATA\n", <DATA>, "---Done\n";
package not::main;
__DATA__
Dog
Cat
Bird
现在你看不到那些行。
---Outputting DATA
---Done
如果你已经阅读了文档,并关心这类事情(或者像我一样,忘记了它),你可能会注意到,DATA
句柄存在于程序末尾作用域中的包中。
在 DATA 之后的内容可以通过文件句柄“PACKNAME::DATA”读取,其中“PACKNAME”是在遇到 DATA 标记时当前包的名称。
我可以通过包指定来获取这些行。
#!/usr/bin/perl
print "---Outputting DATA\n", <not::main::DATA>, "---Done\n";
package not::main;
__DATA__
Dog
Cat
Bird
现在那些行又回来了。
---Outputting DATA
Dog
Cat
Bird
---Done
那么关于 __END__
呢?嗯,那是一个 Perl 4 的事情,在包出现之前。Perl 5 添加了包,然后 Perl 5.6 添加了 __DATA__
标记。__END__
保持以原来的方式做原来的事情(无包),而 __DATA__
通过新的方式做了相关的事情。
为了与在引入 DATA 之前编写的旧脚本兼容,END 在顶层脚本中的行为像 DATA(但在通过“require”或“do”加载的文件中不是)并且通过“main::DATA”留下文件剩余的内容。
其他一些 DATA 技巧
你还可以做几件有趣的事情。
程序大小
你可以使用 -s
文件测试操作符来获取整个文件大小。必须要有 __DATA__
(或 __END__
),但你不需要在这些标记之后有任何数据。
use v5.10;
my $size = -s DATA;
say "File size is $size";
__DATA__
报告的文件大小包括文件中的所有内容,而不仅仅是处理结束之前的部分。
use v5.10;
my $size = -s DATA;
say "File size is $size";
my $data = tell DATA;
say "Data starts at $data";
say "Data size is ", $size - $data
__END__
Dog
Cat
Bird
程序大小包括 __END__
标记和它后面的换行符。其余部分属于数据。
File size is 164
Data starts at 151
Data size is 13
你可以在其他文件操作中使用 DATA
,包括 stat
。
读取两次
如果你想读取数据两次,你可以重置文件光标。首先,在读取任何行之前通过调用 tell
来记住 DATA
开始的位置。当你准备好再次读取它时,通过 seek
到那个相同的位置。
#!/usr/bin/perl
my $data_start = tell DATA;
print "---Outputting DATA\n", <DATA>, "---Done\n";
seek DATA, $data_start, 0;
print "---Outputting DATA\n", <DATA>, "---Done\n";
__END__
Dog
Cat
Bird
使用行号
#!/usr/bin/perl
my $data_start = tell DATA;
print "---Outputting DATA\n";
while( <DATA> ) {
print "$. $_"
}
print "---Done\n";
__END__
Dog
Cat
Bird
现在你看到了一些行号,但它们是从 __DATA__
下的第一行开始的。
---Outputting DATA
1 Dog
2 Cat
3 Bird
---Done
要获取实际的行号,您可以找出__END__
标记的位置。这假设它不在文档中间或字符串中。
#!/usr/bin/perl
my $data_start = tell DATA;
my $end_line;
UNITCHECK {
open my $fh, '<', $0;
while( <$fh> ) { last if /\A__END__$/ }
$end_line = $.
}
print "---Outputting DATA\n";
while( <DATA> ) {
$n = $end_line + $.;
print "$n $_"
}
print "---Done\n";
__END__
Dog
Cat
Bird
现在您可以看到整个文件中的偏移量,而不是__END__
之后的计数。
---Outputting DATA
19 Dog
20 Cat
21 Bird
---Done
有关更多信息,请参阅Can a Perl program know the line number where DATA begins?。
多个嵌入文件
这不是DATA
的事情,但您可以使用Inline::Files创建多个嵌入文件。
#!/usr/bin/perl
use Inline::Files;
print "---Outputting dogs\n", <DOGS>, "---Done\n";
print "---Outputting cats\n", <CATS>, "---Done\n";
print "---Outputting birds\n", <BIRDS>, "---Done\n";
__DOGS__
Rin Tin Tin
Lassie
Ol' Yellar
__CATS__
Grumpy Cat
Garfield
Maru
Mr Bigglesworth
__BIRDS__
Woody Woodpecker
Roadrunner
Zazu
Sam the Eagle
每个文件都有自己的文件句柄。
---Outputting dogs
Rin Tin Tin
Lassie
Ol' Yellar
---Done
---Outputting cats
Grumpy Cat
Garfield
Maru
Mr Bigglesworth
---Done
---Outputting birds
Woody Woodpecker
Roadrunner
Zazu
Sam the Eagle
---Done
Inline::Files有一个问题,因为它覆盖了open
。您必须使用CORE::open
才能访问实际的文件句柄。
use Inline::Files;
print "---Outputting dogs\n", <DOGS>, "---Done\n";
print "---Outputting cats\n", <CATS>, "---Done\n";
print "---Outputting birds\n", <BIRDS>, "---Done\n";
CORE::open my $fh, '<:utf8', '/etc/hosts' or die $!;
print "---Outputting hosts\n", <$fh>, "---Done\n";
标签
brian d foy
brian d foy是一位Perl培训师和作家,同时也是Perl.com的高级编辑。他是《Mastering Perl》、《Mojolicious Web Clients》、《Learning Perl Exercises》的作者,以及《Programming Perl》、《Learning Perl》、《Intermediate Perl》和《Effective Perl Programming》的合著者。
浏览他们的文章
反馈
这篇文章有什么问题吗?请通过在GitHub上打开一个问题或拉取请求来帮助我们。