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?
2 Answers
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
andgetopt
infect to your answer? You start with commentinggetopts
then only mentiongetopt
.– AnthonOct 21, 2014 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 saidgetopts
. Thanks.getopts
doesn't even do long options, so none of this would apply togetopts
. Oct 21, 2014 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 :-)– AnthonOct 21, 2014 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. ☕️– dmmdJan 6, 2017 at 13:20
-
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
getopts
, but you are using the/usr/bin/getopt
command.