2017-04-05 37 views
2

我有一个问题,找到某些文件并从中提取一些数据的子例程。Perl子跳过它被称为的foreach

这个子程序在一个foreach循环中被调用,但是无论何时调用,循环都跳到下一次迭代。所以我想知道是否有任何下一个从子程序逃到它被调用的foreach循环?

据我所知,子看起来很扎实,所以我希望如果有人能看到我失踪的东西?

sub FindKit{ 
    opendir(DH, "$FindBin::Bin\\data"); 
    my @kitfiles = readdir(DH); 
    closedir(DH); 

    my $nametosearch = $_[0]; 
    my $numr = 1; 
    foreach my $kitfile (@kitfiles) 
    { 
     # skip . and .. and Thumbs.db and non-K-files 
     if($kitfile =~ /^\.$/) {shift @kitfiles; next;} 
     if($kitfile =~ /^\.\.$/) {shift @kitfiles; next;} 
     if($kitfile =~ /Thumbs\.db/) {shift @kitfiles; next;} 
     if($kitfile =~ /^[^K]/) {shift @kitfiles; next;} 

     # $kitfile is the file used on this iteration of the loop 
     open (my $fhkits,"<","data\\$kitfile") or die "$!"; 
     while (<$fhkits>) {} 
     if ($. <= 1) { 
      print " Empty File!"; 
      next; 
     } 
     seek($fhkits,0,0); 
     while (my $kitrow = <$fhkits>) { 
      if ($. == 0 && $kitrow =~ /Maakartikel :\s*(\S+)\s+Montagekit.*?($nametosearch)\s{3,}/g) { 
       close $fhkits; 
       return $1; 
      } 
     } 
     $numr++; 
     close $fhkits; 
    } 
    return 0; 
} 
+1

(1)全局子程序中的一些变量(或至少在封闭范围内看到)?有些东西可以被设置,从而触发调用者的代码以跳过其循环。首先,有'$ numr'增加(或不增加),但不在任何地方使用。 (2)返回'($ 1)'是否会导致调用代码跳过它的迭代? – zdim

+0

我已经检查过变量是否在其他地方使用。并且使用$ foundkit =&FindKit($ name)来调用这个子集,所以它将$ foundkit设置为$ 1,但是这个变量在其他地方使用:不在有问题的foreach中被跳过。 – Zyzyx

+1

这段代码需要完整的重写。 (1)当你移动时,你移除_the next_元素。这似乎不是意图(尝试:'perl -E'@ ary = 1..10; for(@ary){say; shift @ary}')(2)如果您想跳过'.'如果$ kitfile eq'。';'(与'..'相同),则执行'next(3)读取整个文件以查看它是否为空? (而且它实际上允许一行!)这就是['-z'](https://perldoc.perl.org/functions/-X.html)的用途(为此您甚至不必打开文件)。 (4)而不是最后一个“while” - 读一行并执行你的条件,然后计数'$ numr ++,而<$fh>'(然后加1); – zdim

回答

1

总结意见,重构代码:

use File::Glob ':bsd_glob'; 

sub FindKit { 
    my $nametosearch = $_[0]; 

    my @kitfiles = glob "$FindBin::Bin/data/K*"; # files that start with K 
    foreach my $kitfile (@kitfiles) 
    { 
     open my $fhkits, '<', $kitfile or die "$!"; 

     my $kitrow_first_line = <$fhkits>;  

     1 while <$fhkits>; # check number of lines ... 

     return if $. == 1; # there was only one line, the header 

     my ($result) = $kitrow_first_line =~ 
      /Maakartikel :\s*(\S+)\s+Montagekit.*?($nametosearch)\s{3,}/; 

     return $result if $result; 
    } 
    return 0; 
} 

我用核心File::Glob并启用:bsd_glob选项,可以在文件名中处理空间。我遵循文档说明在Win32系统上使用“真正的斜杠”。

我不明白这是如何影响调用代码,除了它的返回值。另外,我也没有看到发布的代码如何让调用者跳过节拍。这个问题不太可能出现在这一部分。

请让我知道,如果我错过了上述重写的一点。

+0

' glob“$ FindBin :: Bin/data/K *”'返回完整的路径名,例如你不需要在'open'中指定'data'目录......('open(my $ fhkits,“< ,“data \\ $ kitfile”)'。不是吗? – jm666

+0

我相当肯定会导致'跳过循环'的事情会在'@ kitfiles'被迭代时修改,因为'shift'第一个元素离开阵列并移动其他所有东西' - 所以它会错过的东西。 – Sobrique

+0

@ jm666谢谢!更正。(我把OP从OP提到,而我把'opendir'改成了glob,I甚至在评论中指出...现在引入了一个错误:) – zdim

1

这里几乎肯定会让你感到困扰的是你正在迭代的列表。

这是坏消息,因为你删除元素......但在你不一定在想的地方。

例如:

#!/usr/bin/env perl 

use strict; 
use warnings; 

my @list = qw (one two three); 
my $count; 

foreach my $value (@list) { 
    print "Iteration ", ++$count," value is $value\n"; 
    if ($value eq 'two') { shift @list; next }; 
} 

print "@list"; 

多少次,你认为应该迭代和哪个值在数组中结束了?

因为你shift你永远不会处理元素'三',你删除元素'一'。这几乎可以肯定是什么导致你的问题。

也:

  • open使用相对路径,当你opendir使用绝对的。
  • 跳过一堆文件,然后跳过任何不以K开头的内容。为什么不只是搜索做的事开头K
  • 两次读取文件,一个是检查它是否为空。 perl file test -z将做到这一点很好。
  • 您为文件中的每一行设置了$kitrow,但除了模式匹配之外,并未真正使用它。它可能会更好地使用隐式变量。
  • 您实际上只是在第一行做任何事情 - 因此您不需要遍历整个文件。 ($numr似乎被丢弃)。
  • 您使用全局匹配,但只使用一个结果。 g标志在这里看起来多余。

我建议一个大改写,做这样的事情:

#!/usr/bin/env perl 

use strict; 
use warnings; 
use FindBin; 

sub FindKit{ 
    my ($nametosearch) = @_; 

    my $numr = 1; 
    foreach my $kitfile (glob "$FindBin::Bin\\data\\K*") 
    { 
     if (-z $kitfile) { 
      print "$kitfile is empty\n"; 
      next; 
     } 

     # $kitfile is the file used on this iteration of the loop 
     open (my $fhkits,"<", $kitfile) or die "$!"; 
     <$kitfile> =~ m/Maakartikel :\s*(\S+)\s+Montagekit.*?($nametosearch)\s{3,}/ 
      and return $1; 
     return 0; 
    } 
} 
1

由于Path::Tiny模块的大风扇(我有它总是安装在每一个项目中使用它)我的解决办法将是:

use strict; 
use warnings; 
use Path::Tiny; 

my $found = FindKit('mykit'); 
print "$found\n"; 

sub FindKit { 
    my($nametosearch) = @_; 

    my $datadir = path($0)->realpath->parent->child('data'); 
    die "$datadir doesn't exists" unless -d $datadir; 

    for my $file ($datadir->children(qr /^K/)) { 
     next if -z $file; #skip empty 
     my @lines = $file->lines; 
     return $1 if $lines[0] =~ /Maakartikel :\s*(\S+)\s+Montagekit.*?($nametosearch)\s{3,}/; 
    } 
    return; 
} 

一些意见,仍然打开的问题:

  • 使用Path::Tiny可以在路径名中始终使用正斜杠,而不管操作系统(UNIX/Windows)如何。 data/file也可以在windows上使用。
  • AFAIK的FindBin是considered broken - 因此上述使用$0realpath ...
  • 如果什么工具包是在多个文件?以上总是返回1找到一个
  • my @lines = $file->lines;读取所有行 - 不必要的 - 但对小文件没有什么大不了的。
  • 的现实这个函数返回Maakartikel的ARG,所以也许更好的名字是find_articel_by_kitfind_articel :)
  • 容易切换到utf8 - 只是改变$file->lines$file->lines_utf8;
+0

为此目的使用'$ 0'是安全的;首先是因为它的初始内容因系统而异,其次是因为可以修改,因此可能与程序文件的名称完全不同。 '使用FindBin()'和'“$ FindBin :: Bin/$ FindBin :: Script”''好多了。 – Borodin

+0

@Borodin谢谢。我喜欢学习更多:) – jm666