4

I have a problem I am hoping someone can help with...

I have a foreach loop that executes a backticks command on each iteration, such as greping a folder in a directory for a string (as shown below, greatly simplified for the purposes of explaining my question).

my @folderList = ("/home/bigfolder", "/home/hugefolder", "/home/massivefolder");
my @wordList = ("hello", "goodbye", "dog", "cat");

foreach my $folder (@folderList) {
     foreach my $word (@wordList) {
          print "Searching for this $word in this $folder\n";
          my @output = `grep -R $word $folder`;    #this could take hours so the user needs the option to skip/cancel this iteration and go the next one 
          print "@output\n";
     }
}

The problem I am having:

If the folder the backticks grep command is being run against is particularly large or the array of words to check against is particularly large then the backticks command could take hours to complete (which is fine).

But what i want to be able to do is to break out of the inner loop (i.e when a word is being greped for in a folder) and go to the next iteration if it is taking a long time when the user presses a key on the keyboard or enters the word "next" or "exit" for example.

I know that if i wasnt using backticks I could easily break out of a normal loop using something like the following (but the logic of this obviously does not work when a backticks/system call is involved):

use strict;
use warnings;

use Term::ReadKey;

my $n = 0;

while () {
    print '.';
    last if ReadKey(-1);
    $n++;
}

print $n;

There may be a simple solution that I am overlooking but I have never had the need to do this before, so your help is much appreciated, thanks

4

3 回答 3

2

解决方案是在后台进程中运行长时间运行的程序(并记住新进程的进程 ID),并将用户交互保持在前台进程中。当前台发出中断信号时,终止后台进程。

我提到的所有部分在 Stack Overflow 之前的帖子中都有很好的解释。

于 2013-07-29T16:57:24.813 回答
1

您正在尝试同时运行外部命令和处理键盘事件,因此您需要使用一些异步框架。异步框架基于分叉、线程或事件循环,在这种情况下事件循环是不合适的。

以下是如何使用叉子的概述:

use POSIX ':sys_wait_h';  # defines WNOHANG

foreach my $folder (@folderList) {
     foreach my $word (@wordList) {
          print "Searching for this $word in this $folder\n";

          my $pid = fork();
          if ($pid == 0) {  # child process
              # we are just printing output from the child process; if you want
              # to move data from the child process back to the parent, well,
              # that's a whole other can of worms
              print `grep -R $word $folder`;
              exit;
          } else {          # parent process
              while (waitpid($pid, &WNOHANG) != $pid) {
                  if (Term::ReadKey(-1)) {
                      kill 'TERM', $pid;    # or maybe kill 'KILL', ...
                      last;
                  }
              }
          }
     }
}
于 2013-07-30T14:35:52.470 回答
0

我理解人们对后台进程、线程和分叉等的看法,但最适合我的安排的选项(并且可能更容易实现),尽管我承认可能不是最有效、最佳实践或首选这样做的方式,涉及使用 eval 和捕获用户 control-c 按键。

非常简单的例子:

NEXT:foreach $folder (@folders) {     #label on the foreach

         eval {
               $SIG{INT} = sub { break() };   #catches control-c keypress and calls the break subroutine
               $var1 = `grep -r "hello" $folder`;
         };

         sub break {
              print "Breaking out of the backticks command and going to next folder \n";
              next NEXT;  
         }

     } #ending bracket of foreach loop
于 2013-08-08T11:56:19.277 回答