http://qs321.pair.com?node_id=692382

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

I've got a script that seems a bit clumsy and monotonous (well, a part of it anyway). It's looking something like this:

system("foo --bar baz") == 0 or die "foo failed.\n"; chdir 'foobar' or die "Couldn't chdir to foobar. $!\n"; system("foo --bar baz2") == 0 or die "Couldn't pull it off.\n"; system("foo2 --bar baz") == 0 or die "No joy. foo2 had problems.\n"; chdir '..' or die "Couldn't go back up to base dir. $!\n"; system("bar --foo baz") == 0 or die "Darn. bar didn't make it.\n";

So, there's lots of "or die"'s everywhere.

Is there a more elegant way to do this? Or is what's above pretty standard fare if you want to make sure to bail out if there's an error?

Replies are listed 'Best First'.
Re: Too many "or die" clauses?
by FunkyMonk (Chancellor) on Jun 16, 2008 at 21:46 UTC
    See Fatal (update: included in Perl core), or autodie (update: from CPAN) if you're feeling brave :)

    Unless I state otherwise, all my code runs with strict and warnings
Re: Too many "or die" clauses?
by TGI (Parson) on Jun 16, 2008 at 22:11 UTC

    You could use a dispatch table and a loop to simplify repetitive code.

    use constant { TYPE => 0, ACTION => 1, EXCEPTION => 2, }; my @tasks = ( # TYPE ACTION EXCEPTION [ 'system', "foo --bar baz", "foo failed." ], [ 'chdir', 'foobar', "Couldn't chdir to foobar." ], [ 'system', "foo --bar baz2", "Couldn't pull it off." ], [ 'system', "foo2 --bar baz", "No joy. foo2 had problems." ], [ 'chdir', '..', "Couldn't go back up to base dir." ], [ 'system', "bar --foo baz", "Darn. bar didn't make it." ], ); %TASK_DISPATCHER = ( system => sub { system( $_[ACTION] ) == 0 or die "$_[EXCEPTION]\n"; }, chdir => sub { chdir $_[ACTION] or die "$_[EXCEPTION] $!\n"; }, '' => sub { die "Illegal task type '$_[TYPE]' specified\n"; } ); foreach my $task ( @tasks ) { my $type = exists $TASK_DISPATCHER{$task->[TYPE]} ? $task->[TYPE] : ''; $TASK_DISPATCHER{$type}->( @$task ); }


    TGI says moo

Re: Too many "or die" clauses?
by pc88mxer (Vicar) on Jun 16, 2008 at 21:44 UTC
      autodie is brand new, still in beta. While the developer is working on it heavily, you may want to use its predecessor for now (your call).
Re: Too many "or die" clauses?
by graff (Chancellor) on Jun 17, 2008 at 03:05 UTC
    IMHO, what you have in the OP is "pretty standard fare" for making sure you know as soon as something does not go as expected (e.g. the target of a chdir fails because it happens to be an NFS path that suddenly isn't available).

    I do appreciate TGI's approach, even though it looks a little more cumbersome than it really is. Another approach, if most of your "or die ..." clauses are tacked onto the same functions (system, chdir, a few others?), you could just set up a module with customized versions (and customized names) for these frequently used functions -- e.g. instead of calling "system()", use a module that you call "Safe.pm", and call "safe_system()", which would take an extra arg (either first or last) to supply a message for the "die".

    BTW, I noticed, on reading the Fatal docs for the first time, that it does not support redefining/overriding the system() function, which seems to limit its value in your case.

      I don't think Safe.pm is such a good choice. But otherwise, it's a good idea. Our (yours, mine and bloonix's) suggestions are pretty much the same thing--use a sub to wrap the functions--with one key difference. In mine, the data structure contains the program. In yours, the program contains the program.

      I've made the arguments to the wrapper functions the same to get an apples to apples comparison.

      # Program In Program (PIP) safe_chdir('foo', 'Can't go to foo'); safe_system('foo --bar', 'foo didn\'t work'); safe_chdir('bar', 'bar is unreachable'); # Program In Data (PID) @program = ( [ chdir => 'foo', 'Can't go to foo' ], [ system => 'foo --bar', 'foo didn\'t work' ], [ chdir => 'bar', 'bar is unreachable' ], ); run_program(\@program); # Hiding the dispatch and loop processing in a + sub.

      The program in program (PIP) approach is a bit less cumbersome and simpler than the program in data (PID) method. However, with PID, you can easily modify your program by changing the data structure.

      Is this a strength or a weakness of PID? It really depends on the circumstances and goals of the project. I tend to lean towards a more data driven approach, because I like the flexibility it offers. However, in a program I've been working on over the last week, I use the technique you describe.

      Reflecting upon how I decide on an approach, I find that when I have a small number distinct operations (chdir and system) that must be performed many times as a group, then I use the PID style. However if I have small chunks of repeated code sprinkled around the rest of the program, then I use PIP style.


      TGI says moo

Re: Too many "or die" clauses?
by bloonix (Monk) on Jun 17, 2008 at 10:16 UTC
    Hi, you could wrap system() and chdir().
    sub do_exec { my ($cmd, $args) = @_; system("$cmd $args") == 0 or die "Command '$cmd' failed: $!"; } sub do_chdir { my ($dir) = @_; chdir $dir or die "Couldn't change dir to '$dir': $!"; } sub run { do_exec('foo', '--bar baz'); do_chdir('foobar'); do_exec('foo', '--bar baz2'); do_exec('foo2', '--bar baz'); do_chdir('..'); do_exec('bar', '--foo baz'); } run();
Re: Too many "or die" clauses?
by andreas1234567 (Vicar) on Jun 17, 2008 at 05:38 UTC
    I don't have a problem with the repeated or die. If you wrote tests to accompany your script, e.g. using Test::Exception, that would certainly add elegance to it.
    --
    No matter how great and destructive your problems may seem now, remember, you've probably only seen the tip of them. [1]
Re: Too many "or die" clauses?
by pjf (Curate) on Sep 09, 2008 at 14:22 UTC

    autodie has already been mentioned, but I don't think it's been properly demonstrated just how much it helps in situations like these. Here's the exact same code but using autodie. Note that with a single line you're able to do away with all your die clauses:

    use autodie qw(system chdir); # or qw(:all) system("foo --bar baz"); chdir("foobar"); system("foo --bar baz2"); system("foo --bar baz"); chdir(".."); system("bar --foo baz");

    Yes, autodie works with system, even though Fatal doesn't. If you wrap the whole thing in an eval block, you can even find out what went wrong, the line number, the arguments passed to the function that died, the return value of the process, whether it died to a signal, and all the other things you want. If you don't wrap it in an eval, autodie will include all the relevant information in your error output.

    Unless Rafael has changed his mind, autodie will be included with Perl 5.10.1. Unfortunately it looks like I'm going to miss getting it into 5.8.9 release. In either case, you can download autodie from the CPAN.

    Another excellent option would be IPC::System::Simple. It's pure Perl, has no dependencies, and can be used in a very similar method to autodie:

    use IPC::System::Simple qw(system); use Fatal qw(chdir); system("foo --bar baz"); chdir("foobar"); system("foo --bar baz2"); system("foo --bar baz"); chdir(".."); system("bar --foo baz");

    autodie actually uses IPC::System::Simple underneath to handle calls to system. Given the choice, I'd use autodie, since you can control it with lexical scope, and it provide better inspection of exceptions.

    Disclaimer: I am the author of all the modules mentioned in this post, and therefore think highly of them.

    All the best,

Re: Too many "or die" clauses?
by apl (Monsignor) on Jun 17, 2008 at 10:01 UTC
    Looks nice and solid to me. If you consider the code ugly, stick it in a sub.