15

There is a getopt command in bash command line. getopt can used with short options (such as getopt -o axby "$@"), and can be used with both short and long options (such as getopt -o axby -l long-key -- "$@"), but now I need only long options (i.e. short options don't exist at all), however the command getopt -l long-key -- "$@" doesn't parse --long-key option correctly. So how can I use getopt command with only long options? Or is it impossible or is it just a bug of the getopt command?

  • You tag for the internal getopts, but you are using the /usr/bin/getopt command. – Anthon Oct 17 '14 at 3:24
  • @Anthon Sorry, I have used the wrong tag, but I haven't got enough reputations to add another tag, which needs 300 reputations. However, I have deleted the wrong tag just now. – Victor Oct 21 '14 at 12:43
17

getopt is perfectly fine with having no short options. But you need to tell it that you have no short options. It's a quirk in the syntax — from the manual:

If no -o or --options option is found in the first part, the first parameter of the second part is used as the short options string.

That's what's happening in your test: getopt -l long-key -- --long-key foo treats --long-key as the list of options -egklnoy and foo as the sole argument. Use

getopt -o '' -l long-key -- "$@"

e.g.

$ getopt -l long-key -o '' -- --long-key foo
 --long-key -- 'foo'
$ getopt -l long-key -o '' -- --long-key --not-recognized -n foo
getopt: unrecognized option '--not-recognized'
getopt: invalid option -- 'n'
 --long-key -- 'foo'
  • Did the OP's mixing of getopts and getopt infect to your answer? You start with commenting getopts then only mention getopt. – Anthon Oct 21 '14 at 13:10
  • @Anthon All my answer is about the getopt program from GNU coreutils which is what the question is about. I've fixed the text that said getopts. Thanks. getopts doesn't even do long options, so none of this would apply to getopts. – Gilles 'SO- stop being evil' Oct 21 '14 at 16:36
  • The OP originally had the getopts tag. I did not want to change your answer, because you normally know much better than I what you are writing about :-) – Anthon Oct 21 '14 at 17:01
  • I lost almost an hour trying to figure this out. You just saved me a few vain coffee sips. Thanks... lets put this coffee to a better use now. ☕️ – dmmd Jan 6 '17 at 13:20
1

Dunno about getopt but the getopts builtin can be used to handle only long options like this:

while getopts :-: o
do  case "$o$OPTARG" in
(-longopt1) process ;;
(-longopt2) process ;;
esac; done

Of course, as is, that doesn't work if the long-options are supposed to have arguments. It can be done, though, but, as I've learned working on this. While I initially included it here I realized that for long-options it doesn't have much utility. In this case it was only shortening my case (match) fields by a single, predictable character. Now, what I know, is that it is excellent for short options - it is most useful when it is looping over a string of unknown length and selecting single bytes according to its option-string. But when the option is the arg, there's not a lot you're doing with a for var do case $var in combination that it could do. It is better, I think, to keep it simple.

I suspect the same is true of getopt but I don't know enough about it to say with any certainty. Given the following arg array, I will demonstrate my own little arg parser - which depends primarily on the evalation/assignment relationship I've come to appreciate for alias and $((shell=math)).

set -- this is ignored by default --lopt1 -s 'some '\'' 
args' here --ignored   and these are ignored \
--alsoignored andthis --lopt2 'and 

some "`more' --lopt1 and just a few more

That's the arg string I'll be working with. Now:

aopts() { env - sh -s -- "$@"
} <<OPTCASE 3<<\OPTSCRIPT
acase() case "\$a" in $(fmt='
        (%s) f=%s; aset "?$(($f)):";;\n'
        for a do case "$a" in (--) break;;
        (--*[!_[:alnum:]]*) continue;;
        (--*) printf "$fmt" "$a" "${a#--}";;
        esac;done;printf "$fmt" '--*' ignored)
        (*) aset "" "\$a";;esac
shift "$((SHIFT$$))"; f=ignored; exec <&3 
OPTCASE
aset()  {  alias "$f=$(($f${1:-=$(($f))+}1))"
        [ -n "${2+?}" ] && alias "${f}_$(($f))=$2"; }
for a do acase; done; alias
#END
OPTSCRIPT

That processes the arg array in one of two different ways depending on whether you hand it one or two sets of arguments separated by the -- delimiter. In both cases it applies to sequences of processing to the arg array.

If you call it like:

: $((SHIFT$$=3)); aopts --lopt1 --lopt2 -- "$@"

Its first order of business will be to write its acase() function to look like:

acase() case "$a" in 
    (--lopt1) f=lopt1; aset "?$(($f)):";;
    (--lopt2) f=lopt2; aset "?$(($f)):";;
    (--*) f=ignored; aset "?$(($f)):";;
    (*) aset "" "$a";;esac

And next to shift 3. The command-substitution in the acase() function definition is evaluated when the calling shell builds the function's input here-documents, but acase() is never called or defined in the calling shell. It is called in the subshell, though, of course, and so this way you can dynamically specify the options of interest on the command line.

If you hand it an un-delimited array it simply populates acase() with matches for all arguments beginning with the string --.

The function does practically all of its processing in the subshell - iteratively saving each of the arg's values to aliases assigned with associative names. When it is through it prints out every value it saved with alias - which is POSIX-specified to print all saved values quoted in such a way that their values can be reinput to the shell. So when I do...

aopts --lopt1 --lopt2 -- "$@"

Its output looks like this:

...ignored...
lopt1='8'
lopt1_1='-s'
lopt1_2='some '\'' args'
lopt1_3='here'
lopt1_4='and'
lopt1_5='just'
lopt1_6='a'
lopt1_7='few'
lopt1_8='more'
lopt2='1'
lopt2_1='and

some "`more'

As it walks through the arg list it checks against the case block for a match. If it finds a match there it throws a flag - f=optname. Until it once again finds a valid option it will add each subsequent arg to an array it builds based on the current flag. If the same option is specified multiple times the results compound and do not override. Anything not in case - or any arguments following ignored options - are assigned to an ignored array.

The output is shell-safed for shell-input automatically by the shell, and so:

eval "$(: $((SHIFT$$=3));aopts --lopt1 --lopt2 -- "$@")"

...should be perfectly safe. If it for any reason is not safe, then you should probably file a bug report with your shell maintainer.

It assigns two kinds of alias values for each match. First, it sets a flag - this occurs whether or not an option precedes non-matching arguments. So any occurrence of --flag in the arg list will trigger flag=1. This does not compound - --flag --flag --flag just gets flag=1. This value does increment though - for any arguments that might follow it. It can be used as an index key. After doing the eval above I can do:

printf %s\\n "$lopt1" "$lopt2"

...to get...

8
1

And so:

for o in lopt1 lopt2
do list= i=0; echo "$o = $(($o))"
        while [ "$((i=$i+1))" -le "$(($o))" ]
        do list="$list $o $i \"\${${o}_$i}\" "
done; eval "printf '%s[%02d] = %s\n' $list";  done

OUTPUT

lopt1 = 8
lopt1[01] = -s
lopt1[02] = some ' args
lopt1[03] = here
lopt1[04] = and
lopt1[05] = just
lopt1[06] = a
lopt1[07] = few
lopt1[08] = more
lopt2 = 1
lopt2[01] = and

some "`more

And to args which did not match I would substitute ignored in the above for ... in field to get:

ignored = 10
ignored[01] = this
ignored[02] = is
ignored[03] = ignored
ignored[04] = by
ignored[05] = default
ignored[06] = and
ignored[07] = these
ignored[08] = are
ignored[09] = ignored
ignored[10] = andthis

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.