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

Hi monks! I am writing a PERL script which will act like an interactive shell.
the user will be able to type in only 20 commands
now what i have thought is that i ll use regular expressions to parse what command has been typed in by the user.and then depending on the command i ll execute a system command.
but this way i would end up having 20 if statements.
is there a smarter way of handling this situation where i have to design a Command line interface CLI with 20 commands that a user can type in?

Replies are listed 'Best First'.
Re: how to deal with 20 commands
by Zaxo (Archbishop) on May 23, 2005 at 02:33 UTC

    Use a dispatch table, a hash with your commands as keys and code references as values,

    my %dispatch = ( start => sub { my @args = @_; # . . . }, # . . . ); while (<STDIN>) { my ($cmd, @args) = split; exists $dispatch{$cmd} or warn 'Unknown command: ', $cmd and next; $dispatch{$cmd}(@args); }

    After Compline,
    Zaxo

Re: how to deal with 20 commands
by sk (Curate) on May 23, 2005 at 02:33 UTC
    If it is going to be a simple system commmand like ls, cp etc. you might just be able to get away by having the 20 commands in a Hash... Simple commands first argument is a command itself and you can split on space to get that command and check it in your Hash...

    I am sure there might be other ways to do it but creating an interactive mini-shell might be tricky...for example how do you handle pipes? How do you handle built in functions that throw a message to the user? Remove file xyz (y/n)?: Are you going to parse wild cards or let the shell deal with it?etc.

    If you just want to restrict the commands a user should type you can consider restricting the file perm for the commands that they shouldn't be using. However that does not mean they will have a restrictive shell...

    cheers

    SK

Re: how to deal with 20 commands
by davidrw (Prior) on May 23, 2005 at 02:37 UTC
    I don't know if this is the best way (perhaps there's a module to help maintain this kind of thing?), but you could use something like this with an array of the possible commands.. The array lets you fix the order the cmds are checked for (perhaps putting the common ones first), and if you capture in the regex, the captured values will be pased as parameters to the sub when it's invoked. Below is a working proof of concept:
    use strict; use warnings; my @CMDS = ( { regex => qr/^cp\s+(\S+)\s+(\S+)/, code => \&do_cp }, { regex => qr/^mv\s+(\S+)\s+(\S+)/, code => \&do_mv }, ); my $input_string = <STDIN>; foreach my $cmd ( @CMDS ){ my @match = ($input_string =~ /$cmd->{regex}/) or next; $cmd->{code}( @match ); } sub do_cp { my $src = shift; my $dest = shift; warn "cp $src $dest"; } sub do_mv { my $src = shift; my $dest = shift; warn "mv $src $dest"; }
      thanks for all the help.
      what i am looking for is some thing which works the same way as the module GetOpt::Long or STD works. they parse the command line swtiches and the programmer doesnt habe to worry about if the switches have been types correctly or not
      so is there some thing which can do the same for commands too?

        You could use Getopt::Long:

        local @ARGV = @_; GetOptions( # . . . ) or warn 'Whatever';

        After Compline,
        Zaxo

        You can, of course, do something crazy, and search for 'shell', or 'interactive' on CPAN - maybe someone's been through this before.. it may also reduce the probability that one day one of your users will type
        innocent 'not_really; rm -rf ~/*'
        and walk away whistling...

        seriously, though, read more; ask only when you can show you've put some effort into it...

Re: how to deal with 20 commands
by redhotpenguin (Deacon) on May 23, 2005 at 05:14 UTC
Re: how to deal with 20 commands
by salva (Canon) on May 23, 2005 at 19:37 UTC
    The worst thing about long if-elsif-else chains is their performance, but you are talking about an interactive aplication, so you don't need to worry about performance and chained if-elsif-else's will not be so bad.

    Anyway, let my suggest an alternative OO aproach: define a class for every command you support, i.e.: MyApp::Command::ls, MyApp::Command::cp, etc.

    Then, in every command class define the set of methods you need, i.e. parse_args(), run(), print_output(), etc.

    And then use the perl OO notation to dispatch commands:

    while(read_cmd()} { eval { $cmd_name=~/^\w+/ or die "invalid command"; my $cmd_class = "MyApp::Command::$command_name"; my $cmd = eval { require $cmd_class; $cmd_class->new }; if ($@) die "unknow command $cmd_name ($@)\n"; $cmd->parse_args(@args) or die "..."; $cmd->run(@args) or die "..."; etc(); }; print $@ if $@; }
    This way, adding new commands is very easy, just create new classes for them and drop them on the rigth directory. It also allows for simple code reuse via inheritance.
      The worst thing about long if-elsif-else chains is their performance

      You can order them by expected frequency to help keep O(n/2) low, but I think the worst thing about those chains is their low maintainability.

Re: how to deal with 20 commands
by dwijew (Initiate) on May 23, 2005 at 02:30 UTC
    you can use a switch statement i think. in my opinion its better than 20 if statements!
      Never, never recommend Switch.pm. It's a source filter that has serious bugs and is likely to break your code.


      holli, /regexed monk/

        He didn't recommend Switch.pm.

        And yes, there is an alternative.


        Examine what is said, not who speaks -- Silence betokens consent -- Love the truth but pardon error.
        Lingua non convalesco, consenesco et abolesco. -- Rule 1 has a caveat! -- Who broke the cabal?
        "Science is about questioning the status quo. Questioning authority".
        The "good enough" maybe good enough for the now, and perfection maybe unobtainable, but that should not preclude us from striving for perfection, when time, circumstance or desire allow.