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

After realizing that menus wern't going to cut it, I deicded to simulate a CLI into my program. I have a hash set up as %command_list, with the list of all of my commands, using eval to run the subroutines. example:
%command_list = qw( quit end_program() greeting greeting() time systime() help help() log log_status() );
The problem is multi word commands. When I type "log" into my system, I have the program responds with "Log files inactive, type 'log start' to begin logging. I was planning to have another hash named "log" to then hold the routines for it.
%log = qw(start start_logs() stop stop_logs() ); }
I am using strict and believe this is the problem, but I believe there should be a way to do this while using "strict". Here is the error: Can't use string ("log") as a HASH ref while "strict refs" in use at....... I know the error isn't due to using the word "log".
$command = <STDIN>; # assume $command="log start" @cl =split / /,$command; # if we have multi word command... if (!(defined($cl[1]))){ #single word command chop $cl[0]; $command_action= $command_list{$cl[0]}; } else { #multi word command chop $cl[1]; print "$cl[0]\n"; #command print "$cl[1]\n"; #options to that command $command_action= $cl[0]{$cl[1]}; #trying to set $command_action += to "$log{start}; } eval $command_action; die "\n" if $@; }
Any suggestions on how to fix this || other methods that would work better are greatly appreaciated. Thanks, Thoth

Replies are listed 'Best First'.
(lestrrat) Re: Trying to simulate a CLI using hashes...
by lestrrat (Deacon) on Jan 22, 2002 at 06:11 UTC

    Well, first off, why don't you just make you orignal hash something like this:

    my %commands = ( cmd1 => \&do_cmd1, cmd2 => \&do_cmd2, .... );

    This buys you the flexibility of passing arguments to the subs, if you so wish to

    ## untested, just something I thought up right now chomp( $input = <STDIN> ); my( $cmd, @args ) = split( /\s+/, $input ); if( defined( $commands{ $cmd } ) ) { $command{ $cmd }->( @args ); }

    Obviously you would need to do more error checking and what not, but you get the idea. Also, if you might want to try useing Term::ReadLine and its related modules for soemthing like this. It's quite handy

      Hmmm, when I try to create a hash similar to the one that you suggested, it just runs the routines...I am new to perl, so I'm not sure if I typed something in wrong or not. Program outputs Welcome Ending Program Died at..... I must have mis-understood what you were trying to explain with the hash.
      use strict; my $input; my %commands; my $cmd; my %commands = ( greeting => \&greeting(), quit => \&end_program(), ); chomp( $input = <STDIN> ); my( $cmd, @args ) = split( /\s+/, $input ); if( defined( $commands{ $cmd } ) ) { $commands{ $cmd }->( @args ); } sub greeting { print "Weclome\n"; } sub end_program { print("Ending Program\n"); die; }

        No no, don't put them parathesis! That makes perl evaluate the subroutine, and put the ref to the result of the sub in the hash

        my $rv = mysub(); ## sub is executed. normal my $rv = &mysyb(); ## same as above, but bad style, I think my $rv = \&mysub; ## ref to the subroutine <- this is what we +want my $rv = \&mysub(); ## ref to the return value of the subroutine my $rv = \{ &mysub() }; ## same as above

        UPDATE: wog corrected me: "\{ &mysub() } creates a reference to a refrence to a hash generated from mysub() returns in list context,". I apologize for the mistake :-(

Re: Trying to simulate a CLI using hashes...
by blokhead (Monsignor) on Jan 22, 2002 at 13:18 UTC
    I believe this is your problem:

    $command_action= $cl[0]{$cl[1]};

    In strict, you can't use $cl[0] (contains 'log') as a hash ref (pointing to the %log hash).

    As was mentioned before, you should consider using subroutine references instead of eval'ing. As for the problem with two-word commands, I'd suggest this:

    
    %command_list = (
           quit => \&end_program,
           greeting => \&greeting,
           time => \&systime,
           help => \&help,                        
           log => \&which_log   # this one is new
    );
    
    sub which_log {
      my $arg = shift;
      return start_logs() if ($arg eq "start");
      return stop_logs() if ($arg eq "stop");
      return log_status();  # default behavior
    }
    
    $command = <STDIN>; # assume $command="log start"
    @cl = split / /,$command;
    my $func = shift @cl;   # first CLI argument is hash key
    if (defined($command_list{$func})) {
      $command_list{$func}->(@cl);   # passes rest of CLI args to handler
    } else {
      print "invalid command\n";
    }
    


    This is untested code, but hopefully it does what I think. Whatever the command line entered, it'll take the first arg as the hashkey in %command_list and pass any extra arguments. So just make a simple wrapper for all your *log() subroutines that uses the argument to determine which to call. Hopefully this makes sense ;)
      Thanks for the help, I definitly appreciate it. I did have to make a few changes because of newlines, but I believe that is it.
      use strict; my %command_list; my @cl; my $command; %command_list = ( #quit => \&end_program, greeting => \&greeting, #time => \&systime, #help => \&help, log => \&which_log # this one is new ); while (1){ $command = <STDIN>; # assume $command="log start" @cl = split / /,$command; my $func = shift @cl; # first CLI argument is hash key if (!(defined($cl[0]))){ # had to add this to handle single word c +ommands chop($func); } if (defined($command_list{$func})) { $command_list{$func}->(@cl); # passes rest of CLI args to handl +er } else { print "invalid command\n"; } } sub greeting { print "welcome\n" } sub start_logs() { print "Logs Started\n"; } sub stop_logs() { print "Logs Stopped\n"; } sub log_status { print "log status\n" } sub which_log { my $arg = shift; chop($arg); # newline here also... return start_logs() if ($arg eq "start"); return stop_logs() if ($arg eq "stop"); return log_status(); # default behavior }