I wish to have long and short forms of command line options invoked using my shell script.
I know that getopts
can be used, but like in Perl, I have not been able to do the same with shell.
Any ideas on how this can be done, so that I can use options like:
./shell.sh --copyfile abc.pl /tmp/
./shell.sh -c abc.pl /tmp/
In the above, both the commands mean the same thing to my shell, but using getopts
, I have not been able to implement these?
An improved solution:
I only write shell scripts now and then and fall out of practice, so any feedback is appreciated.
Using the strategy proposed by @Arvid Requate, we noticed some user errors. A user who forgets to include a value will accidentally have the next option's name treated as a value:
will cause the value of "loglevel" to be seen as "--toc=TRUE". This can be avoided.
I adapted some ideas about checking user error for CLI from http://mwiki.wooledge.org/BashFAQ/035 discussion of manual parsing. I inserted error checking into handling both "-" and "--" arguments.
Then I started fiddling around with the syntax, so any errors in here are strictly my fault, not the original authors.
My approach helps users who prefer to enter long with or without the equal sign. That is, it should have same response to "--loglevel 9" as "--loglevel=9". In the --/space method, it is not possible to know for sure if the user forgets an argument, so some guessing is needed.
In case you are starting out on this, there is an interesting difference between "--opt=value" and "--opt value" formats. With the equal sign, the command line argument is seen as "opt=value" and the work to handle that is string parsing, to separate at the "=". In contrast, with "--opt value", the name of the argument is "opt" and we have the challenge of getting the next value supplied in the command line. That's where @Arvid Requate used ${!OPTIND}, the indirect reference. I still don't understand that, well, at all, and comments in BashFAQ seem to warn against that style (http://mywiki.wooledge.org/BashFAQ/006). BTW, I don't think previous poster's comments about importance of OPTIND=$(( $OPTIND + 1 )) are correct. I mean to say, I see no harm from omitting it.
In newest version of this script, flag -v means VERBOSE printout.
Save it in a file called "cli-5.sh", make executable, and any of these will work, or fail in the desired way
Here is example output of the error-checking on user intpu
You should consider turning on -v, because it prints out internals of OPTIND and OPTARG
In case you don't want the
getopt
dependency, you can do this:Of course, then you can't use long style options with one dash. And if you want to add shortened versions (e.g. --verbos instead of --verbose), then you need to add those manually.
But if you are looking to get
getopts
functionality along with long options, this is a simple way to do it.I also put this snippet in a gist.
I have been working on that subject for quite a long time... and made my own library which you will need to source in your main script. See libopt4shell and cd2mpc for an example. Hope it helps !
I don't have enough rep yet to comment or vote his solution up, but sme's answer worked extremely well for me. The only issue I ran into was that the arguments end up wrapped in single-quotes (so I have an strip them out).
I also added some example usages and HELP text. I'll included my slightly extended version here:
getopt
andgetopts
are different beasts, and people seem to have a bit of misunderstanding of what they do.getopts
is a built-in command tobash
to process command-line options in a loop and assign each found option and value in turn to built-in variables, so you can further process them.getopt
, however, is an external utility program, and it doesn't actually process your options for you the way that e.g. bashgetopts
, the PerlGetopt
module or the Pythonoptparse
/argparse
modules do. All thatgetopt
does is canonicalize the options that are passed in — i.e. convert them to a more standard form, so that it's easier for a shell script to process them. For example, an application ofgetopt
might convert the following:into this:
You have to do the actual processing yourself. You don't have to use
getopt
at all if you make various restrictions on the way you can specify options:-o
above), the value has to go as a separate argument (after a space).Why use
getopt
instead ofgetopts
? The basic reason is that only GNUgetopt
gives you support for long-named command-line options.1 (GNUgetopt
is the default on Linux. Mac OS X and FreeBSD come with a basic and not-very-usefulgetopt
, but the GNU version can be installed; see below.)For example, here's an example of using GNU
getopt
, from a script of mine calledjavawrap
:This lets you specify options like
--verbose -dm4096 --minh=20 --maxhe 40 --debugfi="/Users/John Johnson/debug.txt"
or similar. The effect of the call togetopt
is to canonicalize the options to--verbose -d -m 4096 --minheap 20 --maxheap 40 --debugfile "/Users/John Johnson/debug.txt"
so that you can more easily process them. The quoting around"$1"
and"$2"
is important as it ensures that arguments with spaces in them get handled properly.If you delete the first 9 lines (everything up through the
eval set
line), the code will still work! However, your code will be much pickier in what sorts of options it accepts: In particular, you'll have to specify all options in the "canonical" form described above. With the use ofgetopt
, however, you can group single-letter options, use shorter non-ambiguous forms of long-options, use either the--file foo.txt
or--file=foo.txt
style, use either the-m 4096
or-m4096
style, mix options and non-options in any order, etc.getopt
also outputs an error message if unrecognized or ambiguous options are found.NOTE: There are actually two totally different versions of
getopt
, basicgetopt
and GNUgetopt
, with different features and different calling conventions.2 Basicgetopt
is quite broken: Not only does it not handle long options, it also can't even handle embedded spaces inside of arguments or empty arguments, whereasgetopts
does do this right. The above code will not work in basicgetopt
. GNUgetopt
is installed by default on Linux, but on Mac OS X and FreeBSD it needs to be installed separately. On Mac OS X, install MacPorts (http://www.macports.org) and then dosudo port install getopt
to install GNUgetopt
(usually into/opt/local/bin
), and make sure that/opt/local/bin
is in your shell path ahead of/usr/bin
. On FreeBSD, installmisc/getopt
.A quick guide to modifying the example code for your own program: Of the first few lines, all is "boilerplate" that should stay the same, except the line that calls
getopt
. You should change the program name after-n
, specify short options after-o
, and long options after--long
. Put a colon after options that take a value.Finally, if you see code that has just
set
instead ofeval set
, it was written for BSDgetopt
. You should change it to use theeval set
style, which works fine with both versions ofgetopt
, while the plainset
doesn't work right with GNUgetopt
.1Actually,
getopts
inksh93
supports long-named options, but this shell isn't used as often asbash
. Inzsh
, usezparseopts
to get this functionality.2Technically, "GNU
getopt
" is a misnomer; this version was actually written for Linux rather than the GNU project. However, it follows all the GNU conventions, and the term "GNUgetopt
" is commonly used (e.g. on FreeBSD).