Beefy Boxes and Bandwidth Generously Provided by pair Networks
laziness, impatience, and hubris
 
PerlMonks  

Modules which improve system() ?

by pjf (Curate)
on Jun 23, 2006 at 07:51 UTC ( [id://557107]=perlquestion: print w/replies, xml ) Need Help??

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

Ahoy there monks,

I'm writing some material which is primarily aimed at system administrators who'd like to use Perl to make their life easier. Part of that discussion involves demonstrating how to use system() to call external programs, but unfortunately I've hit a snag.

Checking system() to see if anything has gone wrong is a pain. Our command may not actually get run, in which case $? == -1, or it may have been killed by a signal, in which case WIFSIGNALED($?) will be true. It may have run to completion, but the exit code may not have been something that we expect.

Handling the error status from system is always a pain, even in the most simpliest case:

system("some_command") and die "...";

Some less experienced programmers may see the and die and think it is a mistake, since for all other Perl built-ins one would use or die. Even the experienced programmers would be hard-pressed to say what should actually go in the die message. Surely $! should be reported if $? == -1, but otherwise one ends up with an awkward series of statements to unpack signals and exit status. And what if our requirements change later on, and it's okay if some_command also returns an exit status of 1? Surely there must be a better way?

Unfortunately, I can't seem to find a Perl module that makes this easy. I'd like to be able to write code that reads:

use Some::Module qw(run);

run("some_command");

and have run throw an appropriately verbose exception if the command doesn't start, gets zapped by a signal, or returns non-zero. If I later need to allow my command to return a couple of different status values (say 0, 1 and 5), then I can change my code to be:

my $exit_value = run( [0,1,5], "some_command");

and be certain that $exit_value will be either 0, 1 or 5, or an exception will be thrown.

Have I been missing this obviously simple module all this time, and if so, what is it called? (If not, then watch this space for a new module announcement.)

Many thanks,

Update: You can find my solution to the problem using IPC::System::Simple.

Replies are listed 'Best First'.
Re: Modules which improve system() ?
by Gilimanjaro (Hermit) on Jun 23, 2006 at 09:37 UTC

    Ah! A Klingon programmer!

    You would rather have your minions die then return in failure... Well, we can oblige...

    You're looking for Perl6::Builtins, which you can use as follows:

    use Perl6::Builtins qw( system ); system $cmd or croak "Couldn't run: $cmd ($OS_ERROR)";

    Example was taken from TheDamian's Perl Best Practices.

      That's incredibly interesting.
      we can prepare for tomorrow, today!

      are there any downsides, caveats, or cautions to using the Perl6 namespace? for example, are any of these going to change in subtle, bug-introducing ways down the road? or do they cause perl to run incredibly slow or something horrible? it seems too good to be true

      update... i answered my own question, here the dreaded source filter lurks...

      It's not what you look like, when you're doin' what you’re doin'.
      It's what you’re doin' when you’re doin' what you look like you’re doin'!
           - Charles Wright & the Watts 103rd Street Rhythm Band, Express yourself
Re: Modules which improve system() ?
by shmem (Chancellor) on Jun 23, 2006 at 08:49 UTC
    I guess there isn't such a module because for system() all that's about it is in the manual. For running an external programm dealing with the three standard filehandles STDIN, STDOUT and STDERR there's the piped open(FOO,"|-"), IPC::Open2 and IPC::Open3; and then there are IPC::Run and it's brethren.

    Implementing those in a single module a la IO::All could be an interesting experiment, but I expect the outcome just to provide a new flavour or calling convention whilst not providing a shield against the underlying complexity to shine through.

    regards,
    --shmem

    _($_=" "x(1<<5)."?\n".q·/)Oo.  G°\        /
                                  /\_¯/(q    /
    ----------------------------  \__(m.====·.(_("always off the crowd"))."·
    ");sub _{s./.($e="'Itrs `mnsgdq Gdbj O`qkdq")=~y/"-y/#-z/;$e.e && print}
Re: Modules which improve system() ?
by sfink (Deacon) on Jun 25, 2006 at 18:26 UTC
    Have I been missing this obviously simple module all this time, and if so, what is it called? (If not, then watch this space for a new module announcement.)
    Oops, sorry. I skipped over the "watch this space" part when I read this thread, and went off to write the module (since I've wanted this many times too.) It's now uploaded to CPAN as IPC::Run::Simple. If you want to take it over, let me know.

    The module currently allows either:

    use IPC::Run::Simple; run("some", "command") or die "command failed"; # better done as use IPC::Run::Simple qw(:all); run("some", "command") or die "badness! $ERR";
    which is what I normally want, or:
    use IPC::Run::Simple qw(:Fatal); my $exit_value = run(command => [ "some", "command" ], allowed => [ 0, 1, 5 ]);
    for what you want. Or, even closer to what you said:
    use IPC::Run::Simple qw(:Fatal); my $exit_value = run(command => "some_command", allowed => [ 0, 1, 5 ]);

      G'day Steve,

      Firstly, let me say how appreciative I am that you're willing to go forth and write new modules when the need is shown. Unfortunately, your module leaves me in somewhat of a dilemma. It's close in principle to what I want, and almost, but not quite, satisifies what I need.

      My motivation for the module is very specific. I want to make it easier and simplier to do the right thing, than it is to do the wrong thing. Presently, doing the wrong thing is easy:

      system("some_command");

      This doesn't do any sort of error checking, and so is a great spot for a bug to hide. Using IPC::Run::Simple for the basic case does make it easier:

      use IPC::Run::Simple qw(:Fatal);
      run("some_command");

      However for the next most simple case, it suddenly much longer, and not as simple:

      use IPC::Run::Simple qw(:Fatal); my $exit_value = run(command => "some_command", allowed => [ 0, 1, 5 ]);

      I sill feel it provides a simplier interface for this module to get rid of named arguments entirely:

      my $exit_value = run([0, 1, 5], "some_command");

      especially as that makes it easy to modify an existing (single-argument) line to allow new exit values.

      My other big concern is that run() does not throw exceptions by default, which means the developer has to remember to do extra work in order to do the right thing (either with :Fatal, or by using or die).

      It may seem that I'm being incredibly nit-picky, but I'm really trying to make a module that's as fool-proof as can possibly be. I want the default choice to be the right choice even if the developer is ignorant, slack, doesn't read the documentation, and possibly doesn't even understand Perl. Think of your sysadmin archiving files before deleting them. You want that script to die if the archive fails.

      Yes, for me "doing the right thing" == "throwing an exception". Damian discusses this extremely well on pages 274-278 of Perl Best Practices.

      Having said all that, I really want to avoid the CPAN having two modules that do almost the same thing. Goodness knows we have enough of that already.

      I'd very much appreciate further discussion on this topic, and I would love to hear your thoughts.

      All the very best,

        I really have to buy that book, since it's becoming the shared starting point for so many topics.

        I admit I slapped together the API to suit my style, and only incidentally made sure it accomplished your primary goals. Clearly, my preferences don't match yours.

        I find it unexpected that a core function would default to throwing exceptions, and it's a major enough difference that I (as a user) would be surprised if a simple wrapper for a core function changed that part of the API. Very few of Perl's existing builtins throw exceptions. Perhaps they should, but in general I am wary of that approach because it means that the called function is deciding what conditions are exceptional, rather than the caller. For some functions, that seems reasonable, but the return value of external processes doesn't seem like something that can be decided without knowing what the program is and why you're calling it.

        To play the devil's your advocate (sorry), it is reasonable to interpret particular return values as exceptional if a required part of the API is listing out exactly what return values are expected.

        Which brings me to your second point, that the API should be run([allowed retvals], "command", "arg1", "arg2", ...). I just couldn't bring myself to do it this way, because I find the arguments too nonobvious -- if system() were to take an additional array ref argument at the beginning, what should it mean? Judging from the related modules (IPC::Run, IPC::Cmd), it appears that the first choice of an additional argument is a buffer or buffers to place output into. The inference that it is a set of allowed return values is not straightforward to me -- in fact, at first glance at your sample call, I assumed it was a list of file descriptors to do something special with. (But maybe that's just me.)

        The latter is a bigger issue in my mind than the former (whether exceptions are the default.) Once again, if you're in the mindset of "a safe system() replacement that prevents common mistakes", then I think your API is exactly right. But that's not what I expect from a minimalist system() replacement.

        If the replacement were called safe_system(), then I could easily agree with your API choices. Or if the module were named IPC::Run::Safe. Perhaps "safe" isn't the even the right word to describe what you're after, though... "sane", maybe?

        Hmm. So I guess you can either (1) convince me of the error of my ways; (2) come up with another function name that implies what it's doing better (eg safe_system, but preferably shorter!), in which case I'll happily add it to IPC::Run::Simple; or (3) release your own module with a different name. If you choose #3, I might very well use it, because I occasionally do have need of what you've described -- for pretty much exactly the reasons you've laid out; it's just less common than my desire for a simple system() replacement.

Re: Modules which improve system() ?
by Anonymous Monk on Jun 23, 2006 at 08:25 UTC
Re: Modules which improve system() ?
by helgi (Hermit) on Jun 27, 2006 at 10:16 UTC
    I like to use
    system ($cmd) == 0 or die "Error running $cmd:$?\n";

    --
    Regards,
    Helgi Briem
    hbriem AT f-prot DOT com
Re: Modules which improve system() ?
by jesuashok (Curate) on Jun 23, 2006 at 08:14 UTC
    Hi pjf

    Even If I use your Some::Module qw/run/ ( I would say only 'System::Run' ) Inside the module what is your definition is going to be ? what would you use to run my unix commands like, `complicated sed commmands`,runnung another perl programs, running the commands like telnet,ftp,lpr .. etc.

    Efficiency:-

    How the efficiency is going to be when I use the module ( Some::Module)? what are all the factors you are going to consider for efficiency ? Or, you are going to concentrate only on returning the proper error codes and throwing the proper exvceptions.

    I appreciate your good start.

    "Keep pouring your ideas"

      I'm specifically after a module that makes it dead simple to call system() for the most simple cases, and still get proper error-handling. What I essentially want is Fatal for system().

      That means we're not capturing output, and we're not doing complicated pipelines. If you want a module that handles all that and more, then there's IPC::Run. It's complete, but it's not simple.

      What's proper-error handling for the most simple cases? Well, if your program never started, that's an error. If it got killed by a signal or dumped core, then you probably didn't expact that, so that's an error too. If it returns a non-zero exit status, then that's also an error unless you pass an argument to say otherwise.

      As for efficiency, this is a clear case where correctness should easily trump speed. system() is slow to begin with, since there's all that fork-and-exec'ing going on under the hood. Having error-checking that's hard to get wrong (ie, exceptions), and having meaningful error messages (instead of die "Ooops! $! - $?") is going to be much better for correctness and programmer productivity in the long run.

      This is very much a case of wanting something that makes simple things easy. We've already got many ways of making the hard things possible.

      Cheerio,

        As I just mentioned in this post, how about IPC::Run3?

        From its Pod:

        compared to system(), qx'', open "...|", open "|...": + redirects more than one file descriptor + returns TRUE on success, FALSE on failure + throws an error if problems occur in the parent process (or the +pre-exec child) + allows a very perlish interface to Perl data structures and subr +outines + allows 1 word invocations to avoid the shell easily: run3 ["foo"]; # does not invoke shell - does not return the exit code, leaves it in $?

        So you could use it like this:

        eval { run3 $program } or die "Errors running $program";

        Its docs leave a bit to be desired, but the current maintainer might be willing to take some doc patches...

        -xdg

        Code written by xdg and posted on PerlMonks is public domain. It is provided as is with no warranties, express or implied, of any kind. Posted code may not have been tested. Use of posted code is at your own risk.

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlquestion [id://557107]
Approved by Corion
Front-paged by Corion
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others chilling in the Monastery: (4)
As of 2024-03-29 12:39 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found