Pergunta

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

Foi útil?

Solução 3

I understand what people have said regarding background processes, threads and forking and so on, but the option that suited my arrangement the best (and is probably the easier to implement), although I confess may not be the most efficient, best practice or preferred way of doing it, involved using eval and catching user control-c keypresses.

Very Simple Example:

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

Outras dicas

The solution is to run the long-running program in a background process (and remember the process id of the new process), and keep your user interaction in the foreground process. When the foreground is signaled to break, kill the background process.

All the parts I mentioned are well-explained in previous posts on Stack Overflow.

You are trying to simultaneously run an external command and process keyboard events, so you need to use some asynchronous framework. Asynchronous frameworks are based on either forks, threads, or event loops, and event loops are not appropriate in this case.

Here's an outline of how you could use a fork:

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;
                  }
              }
          }
     }
}
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top