2017-04-05 92 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