Beefy Boxes and Bandwidth Generously Provided by pair Networks
Your skill will accomplish
what the force of many cannot
 
PerlMonks  

RFC: Shell::DWIM

by erikharrison (Deacon)
on Oct 06, 2002 at 22:03 UTC ( [id://203218]=perlmeditation: print w/replies, xml ) Need Help??

Here is a little project I'd like a bit of feedback on.

Not to long ago Anony put up this node. It was a little frusterating to watch. Not only was the mistake a common, simple one, but little niggles of system got even BrowserUK. And frankly, who hasn't been bit by it, once or twice, early in their Perl career?

Another niggle is Perl's error vars. Which one to use, when? And, though system calls are rarely portable, almost no one ever remembers to mention $^E on the off chance that the asker is on a VMS, Win32, or OS/2 system.

Both these issues are tidied up in Perl 6. But who wants to wait that long?

So, Shell::DWIM is intended to clean up these little niggles in a consistent way. I hope that it gets itself established enough that we can all point to it whenever a similar question comes down the pipeline. First, a little documentation, as none is currently supplied.

Shell::DWIM currently has no export system, in order to keep things simple for me. It provides two features.

  1. Shell::DWIM::run - This function is a RWIM (return what I mean) replacement for system. It returns true for a succesful execution, false for a failure. It does not support system's indirect object features currently. Unlike system it captures the exception generated by passing it tainted data, and sets $Shell::DWIM::RUN_ERROR appropriately.
  2. $Shell::DWIM::RUN_ERROR - This special variable is set by calls to Shell::DWIM::run. It attempts to be whichever of the four Perl error variables you need, with a few caveats. First, it does not have the number/string magic of $! - it is always stringified. Second, it prefers extended OS errors to the C errno on systems that have extended error support (currently VMS, OS/2, and Win32).

Suggested use:

Shell::DWIM::run $shell_commands or die $Shell::DWIM::RUN_ERROR;

What I ask of the Monastery is a code review and comment. Here are my specific concerns (there are several):

  • run and $RUN_ERROR are terrible names - anyone have better ideas?
  • Currently run traps the exception caused by system $tainted_data. Should it?
  • On the same note, it localizes $@ anticipating that no one will expect the eval{} that run uses internally. Since the other error vars are set correctly, is this application of the Principle of Least Surprise a bit too waterbed like?
  • While there is little that goes on that might be suspect, there is a bit of code between system @_ and the use of $! and $^E (called $OS_ERROR internally). perlvar warns that $! should be used immediatly. Is there a danger there?
  • If $^E is supported, we use it instead of $!. Is this gonna bite anyone in the butt?
  • With a little case specific aliasing, $RUN_ERROR could be made to support $! special magic. But then it could be reset by things other than calls to run. What's your opinion?
  • And finally, How's My Hacking? I'm a bit of a hobbyist programmer, so my style hasn't really gelled yet. Is there anything grossly stupid in the code?

Speaking of which:

package Shell::DWIM; use warnings; use strict; our ($EVAL_ERROR, $CHILD_ERROR, $OS_ERROR, $RUN_ERROR); #Setup internal friendly error names *EVAL_ERROR = \$@; *CHILD_ERROR = \$?; if ($^O =~ /VMS|MSWin|OS\/2/) { #These systems support an extended OS +error message ($^E) *OS_ERROR = \$^E; #$OS_ERROR reflects that value if it e +xists. This clashes somwhat with English.pm } else { *OS_ERROR = \$!; } #The public function sub run { my ($return) = 0; local $EVAL_ERROR; #The eval here is a little unexpected, so we lo +calize $EVAL_ERROR so no one get's bitten eval { $return = (( system @_ ) == 0); }; if ($return) { $RUN_ERROR = undef; } else { SWITCH: { do { $RUN_ERROR = $EVAL_ERROR && last SWITCH } if $EVAL_E +RROR ; do { $RUN_ERROR = $CHILD_ERROR && last SWITCH } if $CHILD_ +ERROR > 0 ; do { $RUN_ERROR = "$OS_ERROR" && last SWITCH } if $CHILD_ +ERROR == -1; #String version of $OS_ERROR LAST_CASE: { $RUN_ERROR = "Exception, package CommandLine: system() fai +led, error unknown"} } } return $return; } 1; #Because you gotta
Cheers,
Erik

Light a man a fire, he's warm for a day. Catch a man on fire, and he's warm for the rest of his life. - Terry Pratchet

Replies are listed 'Best First'.
Re: RFC: Shell::DWIM
by grinder (Bishop) on Oct 07, 2002 at 08:01 UTC

    This is tangentially related to a meditation japhy posted last year about fixing system, and index and rindex as well. Read about it at japhygesis.

    My own preference tends towards the solutions presented in that thread. There's nothing wrong with what you propose, except that I have to remember a lot more details in order to use it, hence more mental effort is required. In light of this, I find it acceptable to redefine system to Do The Right Thing if it makes the code simpler.

    <update>To clarify, Shell::DWIM::run is more to type than system and even at a simpler level 'run' ne 'system'. I have to remember not to try doing a Shell::DWIM::system. And in two months time I'll wonder whether it is run or Run and will have to consult the documentation to check if I remembered correctly. Maybe I'm not a significant data point in the debate after all: I'm comfortable with the concept that system returns the OS error code, and 0 is considered success (which means that in a conditional the test for failure is reversed), and that you get the details of the child though $?.</update>

    This does, of course, have to be traded off against the possibility of subsequent people looking at the source and not immediately grasping the fact that system's return code has been reversed.

    For instance, if you only have one system call it's probably not worth it. But if you're replacing a humungous shell script with dozens of systems, there's a fair chance that adopting this approach will make the code clearer to understand, because it makes them look much more like subroutines and thus you move away from the shellish mindset and into the realm of procedural programming.


    I just thought of something else: I don't like modules that export variables. Sooner or later you'll get someone in serious reality denial who is going to say:

    sub my_job { Shell::DWIM::run( '/dev/null', @_ ); $Shell::DWIM::RUN_ERROR = 0; # Error? There is no error in my code return $Shell::DWIM::RUN_ERROR; }

    To protect yourself, especially as these are read-only values, you want to do something like:

    sub RUN_ERROR { $RUN_ERROR }

    Mind you, people can do that with $!. So if you went ahead and did this, you would offer protection that can't be had by normal means. That in itself would be justification enough for this module.


    print@_{sort keys %_},$/if%_=split//,'= & *a?b:e\f/h^h!j+n,o@o;r$s-t%t#u'

      I had actually considered overloading system, but decided against it, at least as the primary interface. The reason was that the code is destined for two groups pf people - newbies and people writing large scale code who need a more maintainable solution. Overloading system for either group seemed to be the wrong way to go - especially because all of system's features are not supported

      As for remembering more details with this module - what's to remember? It seems that remembering to chain your error variables to find the correct one is harder than print $RUN_ERROR. But then, I wrote Shell::DWIM to minimize my surprise. What exactly is it about the level of detail do you think makes this harder than system or japhygesis?

      Cheers,
      Erik

      Light a man a fire, he's warm for a day. Catch a man on fire, and he's warm for the rest of his life. - Terry Pratchet

Re: RFC: Shell::DWIM
by Zaxo (Archbishop) on Oct 07, 2002 at 16:52 UTC

    The system function is inherently platform specific. Its arguments must be in a form the OS can digest, and available system calls vary widely between platforms. It seems to me that system as it is reminds us to think twice before calling it.

    On *nix, the value returned by system doesn't necessarily reflect an OS error. It may contain an application-specific exit code. In perl, you can write exit -5; to indicate, say, that command line arguments are not to your liking, and provide other exit codes to help diagnose other runtime problems.

    When perl system or a command line process returns, the process run is done and its environment has evaporated. Its $! (errno) is gone. All that is left is what can be recovered by wait. The child error $? encodes the low eight bits of the of the exit code, a signal number and a flag indicating a core dump.

    What distinct information is available on exit from a VMS, Win32 or OS/2 system call? How much different are the extended error returns?

    After Compline,
    Zaxo

      On *nix, the value returned by system doesn't necessarily reflect an OS error

      Which is a common newbie error. They check the value of $! inappropriately after system. According to perlvar, $! (or $^E on some systems) will have useful information after system or backticks if (and only if) $? == -1. Hence the check.

      What distinct information is available on exit from a VMS, Win32 or OS/2 system call? How much different are the extended error returns?

      system returns the same. However, system may fail with $? = -1 when the child process fails due to an OS error. $! is C's errno, but on VMS, Win32, and OS/2 the underlying API provides it's own error messages which C's errno does not reflect. On these systems $^E holds that value (see perlvar for the specific API calls made to get that value). This is a workaround for the fact that Perl and C are still a bit *nix centric. $^E exists for people who aren't on *nix systems while still allowing *nix programmers on Windows or the like still get what they expect from $!.

      Cheers,
      Erik

      Light a man a fire, he's warm for a day. Catch a man on fire, and he's warm for the rest of his life. - Terry Pratchet

Re: RFC: Shell::DWIM
by Aristotle (Chancellor) on Oct 09, 2002 at 20:48 UTC
    I had a bunch of thoughts about this but rather than write a longwinded post, I thought a little code would demonstrate better. (Untested.)
    package Shell::DWIM; use warnings; use strict; our ($RUN_ERROR, $OS_ERR); *OS_ERROR = $^O =~ m{VMS|MSWin|OS/2} ? \$^E : \$!; sub sysdwim { local $@; my $retval = eval { system @_ }; if ($retval == 0) { $RUN_ERROR = undef; return 1; } $RUN_ERROR = $? > 0 ? $? : # non-zero exit code from program $? == -1 ? "$OS_ERR" : # couldn't launch program for some rea +son $@ ? $@ : # eval failed "system() failed with unknown error"; return; } sub import { my $pkg = (caller)[0]; *{$pkg . ($_[0] eq 'override' ? "::system" : "::sysdwim")} = \&sys +dwim; *{$pkg . "::RUN_ERROR"} = \$RUN_ERROR; } 1;
    I'll gladly elaborate if any of my intentions are unclear. This isn't optimal either; it'd be perfect for my taste if it was possible to do a lexical no Shell:DWIM; as well, but I haven't gotten so far in Perl yet.

    Makeshifts last the longest.

Log In?
Username:
Password:

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

How do I use this?Last hourOther CB clients
Other Users?
Others admiring the Monastery: (5)
As of 2024-03-28 20:17 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found