2013-11-04 69 views
1

我正在寫一個小的Perl腳本,主要是爲了學習語言。基本上它有一個動作調度表。基於用戶輸入,將調用任何一個目標動作。每一個動作都是一個小的獨立的效用函數(比如說打印時間),它可以讓我探索和學習perl的不同方面。perl foreach循環問題突圍

我遇到了調度機制的問題。該腳本以連續的循環運行,每次都會收到用戶請求的操作。該輸入與每個可用操作的正則表達式進行比較。如果匹配,則執行該操作,並跳出匹配循環以讀取用戶的下一個請求。我面臨的問題是,如果我兩次要求採取同樣的行動,那麼第二次就不會匹配。如果我在匹配後立即打印調度表,則匹配的操作條目似乎缺失。如果我連續請求相同的操作,它只能在備用調用中使用。如果我避免使用「最後的LABEL」,它的工作原理沒有任何問題。

Perl版本是5.12.4(在Fedora 15,32位上)。以下是一個簡單但完整的例子。我仍然是perl的初學者。請原諒,如果它不符合僧侶的標準:)請幫助解決這個代碼的問題。非常感謝您的幫助。

#!/bin/env perl 

use strict ; 
use warnings ; 
use Text::ParseWords ; 

my @Actions ; 
my $Quit ; 

sub main 
{ 
    # Dispatch table 
    # Each row has [syntax, help, {RegExp1 => Function1, RegExp2 => Function2,...}] 
    # There can be multiple RegExps depending on optional arguments in user input 
    @Actions = 
    (  
     ['first <value>', 'Print first',  {'first (.*)' => \&PrintFirst} ], 
     ['second <value>', 'Print second',  {'second (.*)' => \&PrintSecond} ], 
     ['quit',   'Exits the script', {'quit'  => \&Quit}  ] 
    ) ; 

    my $CommandLine ; 
    my @Captures ; 
    my $RegEx ; 
    my $Function ; 

    while(!$Quit) 
    { 
     # Get user input, repeat until there is some entry 
     while(!$CommandLine) 
     { 
      print "\$ " ; 
      my $argline = <STDIN> ; 
      my @args = shellwords($argline) ; 
      $CommandLine = join (" ", grep { length() } @args) ; 
     } 

     # Find and execute the action matching given input 
     # For each entry in the @Actions array 
     ExecAction: foreach my $Action (@Actions) 
     { 
      # For each RegExp for that action (only 1 per action in this example) 
      while (($RegEx, $Function) = each %{@$Action[2]}) 
      { 
       if (@Captures = $CommandLine =~ m/^$RegEx$/i) 
       { 
        print "Match : $RegEx\n" ; 
        &$Function(@Captures) ; 
        last ExecAction ; # Works if this line is commented 
       } 
      } 
     } 

     $CommandLine = undef ;  
    } 
} 

sub PrintFirst { print "first $_[0]\n" ; } 
sub PrintSecond { print "second $_[0]\n" ; } 
sub Quit  { $Quit = 1 ; } 

main ; 
+1

從http://stackoverflow.com/questions/1123693/why-doesnt-perls-each-iterate-through-the-entire-hash-the-second-time也適用的答覆對你的問題。 –

+0

謝謝。我確實在尋找類似的東西,但沒有任何運氣。 – mpathi

回答

5

您已經偶然發現each運營商的一些微妙行爲。通過在each運算符用盡%{@$Action[2]}的鍵值對之後(last ExecAction)突破循環(last ExecAction),對each %{@$Action[2]}的下一個調用將嘗試檢索不同的鍵 - 值對。由於沒有一個(對於@Action數據結構的每個元素只定義了一個鍵值對),所以each返回一個空列表,並且跳過while循環的內容。

最簡單的解決方法是通過調用keys重置每次使用,比方說之前each相關的「迭代」:

 keys %{@$Action[2]}; 
     while (($RegEx, $Function) = each %{@$Action[2]}) 
     { 
      ... 
     } 

我想明確地複製哈希給一個臨時變量會工作,太:

 my %action_hash = %{@$Action[2]}; 
     while (($RegEx, $Function) = each %action_hash) { 
      ... 
     } 
+0

前者效率很高。後者也適用,因爲迭代器特定於散列,而不是'each'的實例。 – ikegami

+0

@mob感謝您的幫助。從C++背景來看,我甚至沒有讓自己懷疑突破只讀循環是根本原因,儘管我的觀察指向了這個方向。 Perl是一種美麗的語言。但是,在我睡覺之前要走幾英里:)。 – mpathi

5

您需要重置哈希的迭代器,如果你要打破一個each

$ perl -E' 
    %h=(a=>4,b=>5,c=>6,d=>7); 
    while (my ($k, $v) = each %h) { last if ++$i == 2 } 
    keys %h if $ARGV[0]; 
    while (my ($k, $v) = each %h) { say "$k:$v"; } 
' 0 
b:5 
d:7 

$ perl -E' 
    %h=(a=>4,b=>5,c=>6,d=>7); 
    while (my ($k, $v) = each %h) { last if ++$i == 2 } 
    keys %h if $ARGV[0]; 
    while (my ($k, $v) = each %h) { say "$k:$v"; } 
' 1 
c:6 
a:4 
b:5 
d:7 

eachkeys

+0

謝謝。 foreach或其他迭代工具是否也以類似的方式表現?我會盡快查看這些foreach。我越想越想越覺得這種行爲很奇怪。這就像重用垃圾內存位置的值。編碼控制流程中沒有任何內容表明開發人員意圖從迭代的最後一點恢復。 – mpathi

+0

對散列沒有其他迭代功能,除非您計算'keys'和'values',但是這些函數會在一次調用中返回整個列表,所以不可能在中間繼續。 – ikegami