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

Hello, I'm working with a program that uses GetOptions to process command line options.I have the following command line: dowhatever -channel 1 -forever -channel 2 -minutes 10 -hours 20 -channel 3 -forever And my code:
my ( @channel, @minutes, @hours, @forever); GetOptions( " channel:s" => \@channel, "minutes:s" => \@minutes, "hours:s" => \@hours, "forever:s" => \@forever); for ( my $i=0; $i<=$#channel; $i++) { print "Channel number $channel[$i] forever $forever[$i] minutes $m +inutes[$i] hours $hours[$i] \n"; }
For a channel there can be 2 situations : forever or minutes and hours (in the same command line).My problem is that I don't know how to store for each channel it's corresponding situation. For eg. for the upper case I shall have: Channel number 1 forever 1 minutes 0 hours 0 Channel number 2 forever 0 minutes 10 hours 20 Channel number 3 forever 1 minutes 0 hours 0 Or is it possible to do something like this? Thank you very much, bory

Replies are listed 'Best First'.
Re: GetOptions problem
by trammell (Priest) on Jan 21, 2005 at 15:55 UTC
    How about this command-line structure:
    perl foo.pl --channel="1,forever" --channel="2,1:20" \ --channel="3,2:50" --channel="7,forever"
    which is handled by this code:
    #!/usr/bin/perl use strict; use warnings; use Data::Dumper; use Getopt::Long; my @channels; GetOptions('channel=s' => \@channels); print Dumper(\@channels);
    You just have to parse the data out of @channels...
Re: GetOptions problem
by Hena (Friar) on Jan 21, 2005 at 14:45 UTC
    One choice would be to use similar thing as date command. Eg. make a format string for this. Format might be c:h:m (c is channel, h is hour and m is min) with default to forever (eg only channel given). Cli would then be in your example '-channel 1 -channel 2:20:10 -channel 3'. Then you need only one array to put all channel strings.

    But as Tanktalus said. It probably would be better to use config file.
Re: GetOptions problem
by Limbic~Region (Chancellor) on Jan 21, 2005 at 15:03 UTC
    bory,
    As indicated previously, this problem might benefit from a configuration file. If you go this route, I would suggest still using a Getopts:: module to specify the location of the configuration file so you could have multiple versions prepared in advance. I personally like using Config::IniFiles. If you want to avoid an external configuration file, the following seems to meet your needs though I didn't spend a great deal of time testing it:
    #!/usr/bin/perl use strict; use warnings; use Getopt::Std; my %opt; Get_Args( \%opt ); for ( sort { $a <=> $b } keys %opt ) { if ( uc $opt{$_} eq 'F' ) { print "Channel $_ is set to Forever\n"; } elsif ( $opt{$_} =~ /^(\d\d?):(\d\d?)/ ) { my ($hr, $min) = ($1, $2); print "Channel $_ is set to $hr hour(s) and $min minute(s)\n"; } else { die "$opt{$_} is not a valid option for channel $_\n"; } } sub Get_Args { my $opt = shift; my $Usage = qq{Usage: $0 [options] -# <F|HH:MM> -h : This help message. -# : Channel Number followed by duration F = forever HH:MM = Hours and Minutes } . "\n"; my $channels = join ':', 1..10; # Adjust for the max # of channel +s getopts( "h$channels:" , $opt ) or die $Usage; die $Usage if $opt->{h} || ! keys %$opt; }

    Cheers - L~R

Re: GetOptions problem
by Solo (Deacon) on Jan 21, 2005 at 15:04 UTC
    You could use the lone dash trick ( -- ) to separate multiple channel options. Some more thought needs to go into how to trap bad input, however. (This code doesn't handle bad input properly.)

    use Getopt::Long; use Pod::Usage; use YAML; my @config; while (@ARGV) { print join(', ',@ARGV) . "\n"; my %opt; shift if $ARGV[0] eq '-'; GetOptions( \%opt, 'C|channel=i', 'F|forever', 'H|hours=i', 'M|minutes=i', ) or pod2usage(2); push @config, \%opt; } print YAML::Dump(\@config); __END__ =head Synopsis >perl tvopt.pl -C 10 -F -- --channel 1 --hours 10 --minutes 20 -C, 10, -F, --, --channel, 1, --hours, 10, --minutes, 20 --channel, 1, --hours, 10, --minutes, 20 --- #YAML:1.0 - C: 10 F: 1 - C: 1 H: 10 M: 20 =cut

    --Solo

    --
    You said you wanted to be around when I made a mistake; well, this could be it, sweetheart.
Re: GetOptions problem
by Tanktalus (Canon) on Jan 21, 2005 at 14:11 UTC

    To be honest, this sounds complex enough that you want to put it in a config file, not commandline options. I can't even think of a good way to put this on the commandline.

Re: GetOptions problem
by ambrus (Abbot) on Jan 21, 2005 at 16:48 UTC

    You can pass code references to GetOptions, such as

    perl -we 'use Getopt::Long; my @channels; GetOptions("channel=s", sub +{ push @channels, {"channel", $_[1]}; }, "forever", sub { $channels[- +1]{"forever"} = 1; }, "minutes=n", sub { $channels[-1]{"minutes"} = $ +_[1]; }, "hours=n", sub { $channels[-1]{"hours"} = $_[1]; }, ); use D +ata::Dumper; print Dumper [@channels];' -- -channel 1 -forever -chann +el 2 -minutes 10 -hours 20 -channel 3 -forever
    which gives
    $VAR1 = [ { 'forever' => 1, 'channel' => '1' }, { 'hours' => 20, 'minutes' => 10, 'channel' => '2' }, { 'forever' => 1, 'channel' => '3' } ];

    Of course, you may want a different data structure, use whatever is convenient for the rest of your code.

    Update: POSIX getopt (and its extension, GNU's getopt_long) has the correct interface: the right way to get options is to call code provided by the developper. The way perl's Getopt and Getopt::Long tries to be smart and store the data in structures automatically is wrong. It sometimes works, but more often it doesn't, just like this example shows it. It's not too difficult for the developper to write simple callbacks sub { $fooswitch = 1 } instead of a reference \$fooswitch, and it offers much more flexibility.