Haioken has asked for the wisdom of the Perl Monks concerning the following question:

Hi Guys, I'm trying to 'safely' execute a shell command passed to a function (exec_safe). My intention is to return the output of the shell command and the exit code if the command completes. exec_safe receives the following: $_[0] = Command to be run. This may contain pipes, redirects, etc. $_1 = The timeout (seconds) for the command $_2 = A nice value that the command is run at. After some googling, etc I've come up with the following (Comments included):
sub exec_safe { my @cmd; # Return variable eval { local $SIG{ALRM} = sub { die "Timeout\n" }; alarm $_[1]; @cmd = `nice -n $_[2] $_[0]`; alarm 0; }; if($@) { # If command fails, return non-zero and msg return ("Command timeout",1); } else { chomp @cmd; push(@cmd,$? >> 8); return @cmd; } }
While this operates correctly in allowing the perl program to continue after the timeout, in testing it's not terminating the spawned process. For example, if I call 'sleep 250' with a timeout of 10, the function will return after 10 seconds with "Command Timeout",1, however PS shows that the process 'sleep 250' is still running. Caveat: Note that because I'm looking for the returned data and exit code of the child process, exec_safe cannot return until it's finished or timed out. From a brief googlin', it doesn't appear that fork will allow me to get all the data I need. Any ideas?

Replies are listed 'Best First'.
Re: Execute shell command, grab it's return, terminate on timeout
by wind (Priest) on Mar 21, 2014 at 00:52 UTC

    This is a side note about code style suggestions

    1) It really would be wise to use named parameters for all your methods. Your code would become self-documenting and you wouldn't have to preface your code with a description of what your parameters were when posting on perlmonks.

    2) You also have a comment next to @cmd, describing it as a return value. Why not just name it @return_val? Then no need for a comment.

    3) Finally, if you look at the documentation for alarm, you'll see a suggestion on how to propagate errors that don't revolve around the alarm. If your command fails on things other than the Timeout, you're probably going to want to know that.

    sub exec_safe { my ($command, $timeout, $nice_val) = @_; my @return_val; eval { local $SIG{ALRM} = sub { die "Timeout\n" }; alarm $timeout; @return_val= `nice -n $nice_val $command`; alarm 0; }; if($@) { # If command fails, return non-zero and msg die unless $@ eq "Timeout\n"; # propagate unexpected errors return ("Command timeout",1); } else { chomp @return_val; push(@return_val, $? >> 8); return @return_val; } }

    Good luck getting assistance with your requested problem,

    - Miller

      Hi Miller, Thanks for the suggestions.

      1) It's probably left over from days of developing in C for Atmel MicroControllers. Limited memory partly means declaring as few as possible variables. Probably not so much an issue with my servers having >16Gb RAM.

      2) No excuse for that one. Uhhh ... made sense at the time? :o)

      3) I've made this change in my live code, though I'm not sure what circumstances would call it, as the other fails get reported via:

      push(@return_val, $? >> 8);

      The STDOUT data is already in @return_val (or @cmd), and the command above will give me the spawned process' exit code.