Skip Menu |

This queue is for tickets about the Getopt-Long CPAN distribution.

Report information
The Basics
Id: 39052
Status: resolved
Priority: 0/
Queue: Getopt-Long

People
Owner: jv [...] cpan.org
Requestors: markhamnr [...] bigfoot.com
Cc: gregoa [...] cpan.org
RIVY [...] cpan.org
AdminCc:

Bug Information
Severity: Normal
Broken in: 2.36
Fixed in: 2.48



Subject: gnu_getopt option not 100% identical to GNU getopt
The documentation states that Getopt::Long, when configured with 'gnu_getopt', should behave identically to GNU getopt. However, its handling of options with optional arguments differs from GNU getopt: use Getopt::Long 'gnu_getopt'; @ARGV = ('--foo', 'bar'); GetOptions 'foo:s' => \$foo; print $foo, "\n"; $foo should remain undefined here, and 'bar' should be left in @ARGV, to conform to GNU getopt. When I run the above with Perl 5.8.8, however, 'bar' is interpreted as the option to '--foo' and placed in $foo. (The right way to specify this would be with @ARGV = ('--foo=bar'), I claim.) If it's deemed not worthwhile to change or add this behavior, then the documentation should at least be corrected to reflect that 'gnu_getopt' does not completely emulate GNU getopt.
I agree. The behavior of optional parameters is NOT consistent with GNU getopt() and needs to be fixed for the 'gnu_getopt' configuration.
Isn't the actual problem the use of the ":s" specifier? AFAIK, GNU getopt only knows options that either take an argument, or not take an argument. This would mean that the gnu_getopt config setting for Getopt::Long would disable most of its interesting features, limiting itself to the very basic set that GNU getopt supports. I doubt that would be a welcomed change. I think it will be better to change the documentation that the gnu_getopt config setting will emulate the GNU getopt behaviour to a limited but useful extent.
On Sat Sep 26 05:21:35 2015, JV wrote: Show quoted text
> Isn't the actual problem the use of the ":s" specifier? AFAIK, GNU > getopt only knows options that either take an argument, or not take an > argument. > > This would mean that the gnu_getopt config setting for Getopt::Long > would disable most of its interesting features, limiting itself to the > very basic set that GNU getopt supports. I doubt that would be a > welcomed change. > > I think it will be better to change the documentation that the > gnu_getopt config setting will emulate the GNU getopt behaviour to a > limited but useful extent.
No, that's not the behavior as I understand it. The processing as described in Mark's earlier post is what I've seen to be the usual, unsurprising behavior. For mandatory arguments (eg, '=s'), the behavior should be as currently written, eg, an optional equal sign with consumption of the next token as the option's argument. That token may be the split off `$optarg` portion or the next argument in the array/string. But for optional arguments (eg, ':s'), if an argument is supplied for the option, it must be signaled by an equal sign. No equal sign means no argument. From "http://linux.die.net/man/1/getopt", "If the option has an optional argument, it must be written directly after the long option name, separated by '=', if present". This creates the behavior noted in the first post, so that neither `app --foo=x bar` nor `app --foo bar` consumes bar as foo's argument. Again, this is the general behavior that I've seen and come to expect. The consumption of bar as foo's argument in the second case is surprising, as is the resultant need to use `app --foo= bar` to avoid bar's consumption. I don't think fixing this causes any harm to the usefulness of the module, whether in `gnu_compat` mode or not. Here is a simple change that fixes the behavior without disabling any functionality and passes all tests... I think it's the better solution, although I don't know the code well enough to say that passing all tests is sufficient to certify the patch. diff for `lib/Getopt/Long.pm`: ====<cut> @@ -1109,9 +1109,8 @@ sub FindOption ($$$$$) { my $mand = $ctl->[CTL_AMIN]; # Check if there is an option argument available. - if ( $gnu_compat && defined $optarg && $optarg eq '' ) { - return (1, $opt, $ctl, $type eq 's' ? '' : 0) ;#unless $mand; - $optarg = 0 unless $type eq 's'; + if ( $gnu_compat && (( defined $optarg && $optarg eq '' ) || not $mand ) ) { + return (1, $opt, $ctl, $type eq 's' ? '' : 0) ;#unless $mand; } # Check if there is an option argument available. ====<cut> - Roy
Unfortunately, that trips one of the regression tests. Please try the attached patch against Getopt::Long 2.47.
Subject: try.patch
diff --git a/lib/Getopt/Long.pm b/lib/Getopt/Long.pm index cce60f5..4d93171 100644 --- a/lib/Getopt/Long.pm +++ b/lib/Getopt/Long.pm @@ -940,6 +940,7 @@ sub FindOption ($$$$$) { print STDERR ("=> split \"$starter\"+\"$opt\"\n") if $debug; my $optarg; # value supplied with --opt=value + my $optargtype = 0; # 0 = none, 1 = empty, 2 = nonempty my $rest; # remainder from unbundling # If it is a long option, it may include the value. @@ -952,6 +953,7 @@ sub FindOption ($$$$$) { $optarg = substr($optorg, $oppos + 1); # retain tainedness print STDERR ("=> option \"", $opt, "\", optarg = \"$optarg\"\n") if $debug; + $optargtype = length($optarg) ? 2 : 1; } #### Look it up ### @@ -1109,9 +1111,12 @@ sub FindOption ($$$$$) { my $mand = $ctl->[CTL_AMIN]; # Check if there is an option argument available. - if ( $gnu_compat && defined $optarg && $optarg eq '' ) { - return (1, $opt, $ctl, $type eq 's' ? '' : 0) ;#unless $mand; - $optarg = 0 unless $type eq 's'; + if ( $gnu_compat ) { + # GNU getopt requires '=' when the value is optional. + return (1, $opt, $ctl, $type eq 's' ? '' : 0) + if $optargtype == 1; # --foo= -> return nothing + return (1, $opt, $ctl, undef) + unless $optargtype || $mand; } # Check if there is an option argument available.
On Mon Sep 28 14:36:38 2015, JV wrote: Show quoted text
> Unfortunately, that trips one of the regression tests. > > Please try the attached patch against Getopt::Long 2.47.
Can you point me toward the regression tests? The change I previously posted passes all the tests in the distribution, and I can't test alternatives without access to all the tests. Your change corrects the behavior but opens a hole... Given `getopt(\%OPT,'foo:s')`, `app --foo` differs from `app --foo=`. The first `--foo` just swallows the option, as if it didn't occur (using 'foo:s'=>sub{} doesn't even trigger the sub). I believe the behavior of `--foo` should be the same as `--foo=` or `--foo=""`, eg, $OPT{foo} should be set equal to ''; otherwise `--foo` by itself is useless. `--foo` == `--foo=` == `--foo=""` seems to be the intuitive behavior for me, and it's in keeping with the current Getopt::Long behavior for optional arguments (eg, "if no suitable value is supplied, string valued options get an empty string '' assigned, while numeric options are set to 0"). Thanks for looking into this... - Roy
On Mon Sep 28 14:36:38 2015, JV wrote: Show quoted text
> Unfortunately, that trips one of the regression tests. > > Please try the attached patch against Getopt::Long 2.47.
Hmm, this also seems to break single character options with optional arguments. For example, using the config 'f:s', `-fx` consumes 'x' but doesn't set 'f'. Try changing: ``` if ( $gnu_compat ) { # GNU getopt requires '=' when the value is optional. return (1, $opt, $ctl, $type eq 's' ? '' : 0) if $optargtype == 1; # --foo= -> return nothing return (1, $opt, $ctl, undef) unless $optargtype || $mand; } ``` to ``` if ( $gnu_compat && (length($opt) > 1) ) { # GNU getopt requires '=' when the value is optional. return (1, $opt, $ctl, $type eq 's' ? '' : 0) if $optargtype <= 1; # --foo= -> return nothing return (1, $opt, $ctl, undef) unless $optargtype || $mand; } ``` That seems to fix the issues for me.
On Wed Sep 30 03:10:57 2015, RIVY wrote: Show quoted text
> On Mon Sep 28 14:36:38 2015, JV wrote:
> > Unfortunately, that trips one of the regression tests. > > > > Please try the attached patch against Getopt::Long 2.47.
> > Hmm, this also seems to break single character options with optional > arguments. For example, using the config 'f:s', `-fx` consumes 'x' but > doesn't set 'f'. Try changing: > > ``` > if ( $gnu_compat ) { > # GNU getopt requires '=' when the value is optional. > return (1, $opt, $ctl, $type eq 's' ? '' : 0) > if $optargtype == 1; # --foo= -> return nothing > return (1, $opt, $ctl, undef) > unless $optargtype || $mand; > } > ``` > > to > > ``` > if ( $gnu_compat && (length($opt) > 1) ) { > # GNU getopt requires '=' when the value is optional. > return (1, $opt, $ctl, $type eq 's' ? '' : 0) > if $optargtype <= 1; # --foo= -> return nothing > return (1, $opt, $ctl, undef) > unless $optargtype || $mand; > } > > ``` > > That seems to fix the issues for me.
I'd also change the comments: `# GNU getopt requires '=' when the value is optional.` to `# GNU getopt requires '=' to signal a value, when that value is optional.` and `# --foo= -> return nothing` to `# --foo or --foo= -> return ''/0`.
Subject: Re: [rt.cpan.org #39052] gnu_getopt option not 100% identical to GNU getopt
Date: Wed, 30 Sep 2015 13:40:05 +0200
To: bug-Getopt-Long [...] rt.cpan.org
From: Johan Vromans <jvromans [...] squirrel.nl>
Show quoted text
> Hmm, this also seems to break single character options with optional > arguments. For example, using the config 'f:s', `-fx` consumes 'x' but > doesn't set 'f'.
I get "Unknown option: fx", which is correct since gnu_compat disables bundling.
On Wed Sep 30 07:40:19 2015, jvromans@squirrel.nl wrote: Show quoted text
> > Hmm, this also seems to break single character options with optional > > arguments. For example, using the config 'f:s', `-fx` consumes 'x' but > > doesn't set 'f'.
> > I get "Unknown option: fx", which is correct since gnu_compat disables > bundling.
Yes, my mistake, I was being imprecise and was testing using a script with `Getopt::Long::Configure('gnu_getopt');`. When I use `Getopt::Long::Configure('gnu_compat');`, I get the same result. But, isn't it that still incorrect for both modes? It's not a bundled option, it's a single option taking an optional value with it's argument. `-oarg` is used in both getopt() and getopt_long() documents as an example of an option with it's corresponding 'optarg' argument. But, before tracking that down, does the last patch trip any issues? - Roy
Ok, this included diff (based on 'Getopt-Long-2.47') passes all regression tests. I believe it has the desired long option behavior and the correct single character option behavior, as well. I've included updated regression tests under separate cover, via email. Note, this doesn't create the `--foo` == `--foo=""` behavior that I was speaking in-favor of previously. But, despite my misgivings about `--foo` just disappearing, after re-reading the docs, I think this is the correct behavior. - Roy ``` diff --git "a/..\\Getopt-Long-2.47\\lib\\Getopt\\Long.pm" "b/lib\\Getopt\\Long.pm" index cce60f5..bc75486 100644 --- "a/..\\Getopt-Long-2.47\\lib\\Getopt\\Long.pm" +++ "b/lib\\Getopt\\Long.pm" @@ -1109,9 +1109,13 @@ sub FindOption ($$$$$) { my $mand = $ctl->[CTL_AMIN]; # Check if there is an option argument available. - if ( $gnu_compat && defined $optarg && $optarg eq '' ) { - return (1, $opt, $ctl, $type eq 's' ? '' : 0) ;#unless $mand; - $optarg = 0 unless $type eq 's'; + if ( $gnu_compat ) { + my $optargtype = 0; # 0 = none, 1 = empty, 2 = nonempty + $optargtype = ( !defined($optarg) ? 0 : ( (length($optarg) == 0) ? 1 : 2 ) ); + return (1, $opt, $ctl, undef) + if (($optargtype == 0) && !$mand); + return (1, $opt, $ctl, $type eq 's' ? '' : 0) + if $optargtype == 1; # --foo= -> return nothing } # Check if there is an option argument available. @@ -1359,6 +1363,8 @@ sub Configure (@) { } elsif ( $try eq 'gnu_compat' ) { $gnu_compat = $action; + $bundling = 0; + $bundling_values = 1; } elsif ( $try =~ /^(auto_?)?version$/ ) { $auto_version = $action; ```
This seems to be fixed in 2.48 (introducing a regression discussed in https://rt.cpan.org/Ticket/Display.html?id=114999 )