问题描述
我正在寻找一个充满文本文件的文件夹,并将值存储在哈希表中。示例文本文件如下所示:
sample.txt
title: Sample Soft
version: 1
category: utility
os_support: Linux
date_added: 01/01/1998
# Lines beginning '#' are skipped
This is a new line.
This is <b>bold</b> to test HTML.
我编写了一个Perl子例程,该子例程将文件名作为参数。我的Perl子程序如下所示:
sub read_entry {
my $key = $_[0];
open (ENTRY,"$entrydir/$key") or die "Couldn't open $entrydir/$key";
$key =~ s/\.txt//;
while (<ENTRY>) {
if (my @entrykv = split /:\s*/) {
# store key-value pair here,key value is $key
print @entrykv;
} elsif (! /^#/) {
say "NOT COMMENT";
$desc{$key} += $_;
}
}
close ENTRY;
}
我正在做的是遍历文本文件中的每一行。当它匹配以冒号结尾的 descriptor 时(例如title:
),它将在行的右侧存储相应的 value (例如Sample Soft
)放入哈希图中。
我有为这些描述符命名的哈希图,这些哈希图是从文本文件中读取的。文本文件的第一行应读入名为“ title”的哈希图中。切掉.txt
的文本文件名是这些哈希表的键值,即我代码中的变量$key
。例如,如果我们读“ sample.txt”,则该值为“ sample”。因此,将有一个标题,版本,类别和&c的哈希图,其值由从文本文件名中提取的$ key变量索引。
$key = "sample";
$title{$key} = "Sample Soft";
$version{$key} = "1";
$category{$key} = "utility";
$os_support{$key} = "Linux";
$date_added{$key} = "01/01/1998";
$desc{$key} = "This is a new line.\nThis is <b>bold</b> to test HTML.";
输入还应忽略任何注释(以#开头的行),文本文件的其余部分(与描述符值格式不匹配)将存储在$desc{$key}
哈希图中。
split函数有效,与此同时,我只打印了刚刚拆分的内容。但是,这些值尚未存储在哈希图中。另外,if条件不起作用。我仍然看到评论和说明。
非常感谢您对解决条件和完成键值读取的任何帮助。
解决方法
这就是我的处理方式。
构建正则表达式以从文件中提取已知条目。
跳过评论是第一件事。您不想调查以八面体开头的线。
使用哈希散列存储详细信息。您不必根据要提取的详细信息进行分支。
#!/usr/bin/perl
use warnings;
use strict;
my $entrydir = '.';
my @recognised = qw( title version category os_support date_added );
my $regex = join '|',map quotemeta,@recognised;
$regex = qr/^($regex):/; #/ stupid SO highlighter
sub read_entry {
my ($key) = @_;
open my $in,'<',"$entrydir/$key" or die $!;
$key =~ s/\.txt$//; #/ stupid SO highlighter
my %details;
while (<$in>) {
next if /^#/;
if (/$regex(.*)/) {
$details{$1} = $2;
} else {
$details{desc} .= $_;
}
}
return $key,\%details
}
my %software;
for my $file (@ARGV) {
my ($key,$details) = read_entry($file);
$software{$key} = $details;
}
use Data::Dumper; print Dumper \%software;
,
这是解决方案。您可以使用哈希散列存储数据值。
#!/usr/bin/perl
use strict;
use warnings;
use Data::Dumper;
use File::Basename;
no warnings 'uninitialized';
my $path = '/home/location/mention/sample.txt'; #either pass as an argument $ARGV[0]
my $fname = basename($path);
my $f;
if ($fname =~ /(\w+).txt/) { $f = $1; }
open my $fh,$path or die "Cannot open file: $path\n";
my %hash = ();
my ($title,$ver,$category,$os,$date,$description);
while (<$fh>){
chomp;
next if($_ =~ /^#/);
if( $_ =~ /:/){
if( $_ =~ /title: (.*)/) { $hash{$f}{'TITLE'} = $1; }
if( $_ =~ /version: (\d+)/) { $hash{$f}{'VERSION'} = $1; }
if( $_ =~ /category: (\w+)/) { $hash{$f}{'CATEGORY'} = $1; }
if( $_ =~ /os_support: (\w+)/){ $hash{$f}{'OS'} = $1; }
if( $_ =~ /date_added: (.*)/) { $hash{$f}{'DATE'} = $1; }
} else {
$description = $_;
$hash{$f}{'DESC'} = $hash{$f}{'DESC'}.$description;
}
}
print Dumper(\%hash);
close $fh;
在%hash
转储器中,您可以看到内容的存储方式。
PS:从每行regex
读取数据可以得到改善。
您有一些解决方案,但是我认为解释现有代码中的一些问题会很有趣。
您说:
此外,if条件不起作用。我仍然看到评论和说明。
让我们先来看一下。
您的条件是:
if (my @entrykv = split /:\s*/) {
...
} else {
...
}
您希望这样做可以区分键/值行和包含描述的行(因此,不包含“:”)。我认为您误解了split()
的功能。如果该行包含“:”,则split()
返回一个包含两个项目的列表。该列表被放入数组@entrykv
中,并且if
语句在标量上下文中对该数组求值,标量上下文给出的值为2(因为数组中有两个元素)。 2是一个真值,因此我们进入if
/ else
语句的“真”部分。
在该行中没有“:”的情况下,split()
将返回一个包含单个元素(包含整行)的列表。同样,将其放入@entrykv
中,并且if
语句在标量上下文中对其进行评估。这样得出的值1和1仍然是真实值,并且我们仍然以if
/ else
语句的“真实”部分结束。
所以您需要在这里做一些不同的事情。最好的方法可能是检查该行中是否存在“:”。像这样:
if (/:/) {
my @entrykv = split /:\s*/; #/ stupid SO highlighter
print "@entrykv\n";
} elsif (! /^#/) {
print "Not a comment\n";
$desc{$key} .= $_;
}
哦,这是第二点。您使用+=
添加到描述的末尾。那是不对的。 Perl使用.
作为串联运算符-因此,您在这里需要.=
。
这里还有另一个问题直接涉及到设计的核心。每个属性都有单独的哈希。那真是个坏主意。如果您的数据项具有许多属性,则应尝试将所有这些属性放在一个变量中。哈希是将所有这些属性存储在一起的理想方法。
您的read_entry()
子例程读取单个文件。而且我认为每个文件仅包含一个“键”(例如“样本”)。因此,我认为该子例程返回对包含该文件中所有数据的哈希的引用是有意义的。可能看起来像这样:
sub read_entry {
my ($filename) = @_;
my $key = $filename;
$key =~ s/\.txt$//; #/ stupid SO highlighter
# use a) lexical filehandle and b) three-arg version of open()
open my $fh,$filename or die "Can't open $filename: $!\n";
my %data;
while (<$fh>) {
# Skip comments
next if /^#/;
if (/:/) {
chomp;
my ($key,$val) = split /:\s*/; #/ stupid SO highlighter
$data{$key} = $val;
} else {
$data{desc} .= $_;
}
}
return \%data;
}