Is there a CPAN way to parse random command line o

2019-07-16 08:45发布

Is there a good (ideally CPAN) way to process arbitrary command line options with Perl?

E.g., take a string "-a 1 -b 'z -x' -c -d 3 4" and generate a GetOpt::Long - like data structure:

 { a=>1, b=>"z -x", c=>1, d=>[3,4] }  # d=>"3 4" is acceptable

The caveat is that

  1. the set of options is NOT known in advance; so we seemingly can't use GetOpt::Long as-is.

  2. The values themselves can contain other options, so simply parsing the string for #\b+-(\S+)\b# pattern to find all the options in THAT string also seems to not be possible, and further complicated that some parameters are of =s type, some =s@, some of "-x a b c" type.

  3. Moreover, even if we could do #2, does GetOptionsFromString support correct tokenizing that respects quoted values?


NOTE: Assume for the purpose of exercise that ALL arguments are "options", in other words, if you split up the string into (possibly-quoted) tokens, your structure is always

 "-opt1 arg1a [arg1b] -opt2 ....".

In other words, any word/token that starts with a dash is a new option, and all the subsequent words/tokens that do NOT start with a dash are values for that option.

2条回答
劫难
2楼-- · 2019-07-16 08:58

Before I knew about Getopt::Long and the wisdom of using it, I rolled my own command line options processor that would take arbitrary arguments and populate a global hashtable. The rules were

Switches with a single letter (-A .. -Z, -a .. -z)

-n               sets  $args{"n"} = 1
-nfoo            sets  $args{"n"} = "foo"

Switches with more than one letter

--foo            sets  $args{"foo"} = 1
--foo=bar        sets  $args{"foo"} = "bar"

The joy and the hurt of this approach was you could quickly experiment with new command line options, changing code at the point that the option would be used without having to edit the call to GetOptions or having to allocation another variable:

 ... line 980 ...
 if ($args{"do-experimental-thing"}) {
     # new code
     do_experimental_thing();
 } else {
     do_normal_thing();
 }

This was the first module I uploaded to CPAN and I've since removed it, but the BackPAN has a long memory.

查看更多
迷人小祖宗
3楼-- · 2019-07-16 09:01

A quick example using Text::Parsewords and a simple state machine.

#!/usr/bin/env perl

use strict;
use warnings;

use Text::ParseWords qw/shellwords/;

my $str = q{-a 1 -b 'z -x' -c -d 3 4};
my $data = parse($str);

use Data::Printer;
p $data;

sub parse {
  my $str = shift;

  my @tokens = shellwords $str;

  my %data;
  my @keys;
  my $key = '_unknown';
  foreach my $token (@tokens) {
    if ($token =~ s/^\-//) {
      $key = $token;
      push @keys, $key;
      next;
    }

    if ( ref $data{$key} ) {
      push @{ $data{$key} }, $token;
    } elsif (defined $data{$key}) {
      $data{$key} = [ $data{$key}, $token ];
    } else {
      $data{$key} = $token;
    }
  }

  foreach my $key (@keys) {
    next if defined $data{$key};
    $data{$key} = 1;
  }

  return \%data;
}
查看更多
登录 后发表回答