Bash Space-Separated (e.g., --option argument
)
cat >/tmp/demo-space-separated.sh <<'EOF'
#!/bin/bash
POSITIONAL=()
while [[ $# -gt 0 ]]; do
key="$1"
case $key in
-e|--extension)
EXTENSION="$2"
shift # past argument
shift # past value
;;
-s|--searchpath)
SEARCHPATH="$2"
shift # past argument
shift # past value
;;
-l|--lib)
LIBPATH="$2"
shift # past argument
shift # past value
;;
--default)
DEFAULT=YES
shift # past argument
;;
*) # unknown option
POSITIONAL+=("$1") # save it in an array for later
shift # past argument
;;
esac
done
set -- "${POSITIONAL[@]}" # restore positional parameters
echo "FILE EXTENSION = ${EXTENSION}"
echo "SEARCH PATH = ${SEARCHPATH}"
echo "LIBRARY PATH = ${LIBPATH}"
echo "DEFAULT = ${DEFAULT}"
echo "Number files in SEARCH PATH with EXTENSION:" $(ls -1 "${SEARCHPATH}"/*."${EXTENSION}" | wc -l)
if [[ -n $1 ]]; then
echo "Last line of file specified as non-opt/last argument:"
tail -1 "$1"
fi
EOF
chmod +x /tmp/demo-space-separated.sh
/tmp/demo-space-separated.sh -e conf -s /etc -l /usr/lib /etc/hosts
Output from copy-pasting the block above
FILE EXTENSION = conf
SEARCH PATH = /etc
LIBRARY PATH = /usr/lib
DEFAULT =
Number files in SEARCH PATH with EXTENSION: 14
Last line of file specified as non-opt/last argument:
#93.184.216.34 example.com
Usage
demo-space-separated.sh -e conf -s /etc -l /usr/lib /etc/hosts
Bash Equals-Separated (e.g., --option=argument
)
cat >/tmp/demo-equals-separated.sh <<'EOF'
#!/bin/bash
for i in "$@"; do
case $i in
-e=*|--extension=*)
EXTENSION="${i#*=}"
shift # past argument=value
;;
-s=*|--searchpath=*)
SEARCHPATH="${i#*=}"
shift # past argument=value
;;
-l=*|--lib=*)
LIBPATH="${i#*=}"
shift # past argument=value
;;
--default)
DEFAULT=YES
shift # past argument with no value
;;
*)
# unknown option
;;
esac
done
echo "FILE EXTENSION = ${EXTENSION}"
echo "SEARCH PATH = ${SEARCHPATH}"
echo "LIBRARY PATH = ${LIBPATH}"
echo "DEFAULT = ${DEFAULT}"
echo "Number files in SEARCH PATH with EXTENSION:" $(ls -1 "${SEARCHPATH}"/*."${EXTENSION}" | wc -l)
if [[ -n $1 ]]; then
echo "Last line of file specified as non-opt/last argument:"
tail -1 $1
fi
EOF
chmod +x /tmp/demo-equals-separated.sh
/tmp/demo-equals-separated.sh -e=conf -s=/etc -l=/usr/lib /etc/hosts
Output from copy-pasting the block above
FILE EXTENSION = conf
SEARCH PATH = /etc
LIBRARY PATH = /usr/lib
DEFAULT =
Number files in SEARCH PATH with EXTENSION: 14
Last line of file specified as non-opt/last argument:
#93.184.216.34 example.com
Usage
demo-equals-separated.sh -e=conf -s=/etc -l=/usr/lib /etc/hosts
To better understand ${i#*=}
search for "Substring Removal" in this guide. It is functionally equivalent to `sed 's/[^=]*=//' <<< "$i"`
which calls a needless subprocess or `echo "$i" | sed 's/[^=]*=//'`
which calls two needless subprocesses.
Using bash with getopt[s]
getopt(1) limitations (older, relatively-recent getopt
versions):
- can't handle arguments that are empty strings
- can't handle arguments with embedded whitespace
More recent getopt
versions don't have these limitations. For more information, see these docs.
POSIX getopts
Additionally, the POSIX shell and others offer getopts
which doen't have these limitations. I've included a simplistic getopts
example.
cat >/tmp/demo-getopts.sh <<'EOF'
#!/bin/sh
# A POSIX variable
OPTIND=1 # Reset in case getopts has been used previously in the shell.
# Initialize our own variables:
output_file=""
verbose=0
while getopts "h?vf:" opt; do
case "$opt" in
h|\?)
show_help
exit 0
;;
v) verbose=1
;;
f) output_file=$OPTARG
;;
esac
done
shift $((OPTIND-1))
[ "${1:-}" = "--" ] && shift
echo "verbose=$verbose, output_file='$output_file', Leftovers: $@"
EOF
chmod +x /tmp/demo-getopts.sh
/tmp/demo-getopts.sh -vf /etc/hosts foo bar
Output from copy-pasting the block above
verbose=1, output_file='/etc/hosts', Leftovers: foo bar
Usage
demo-getopts.sh -vf /etc/hosts foo bar
The advantages of getopts
are:
- It's more portable, and will work in other shells like
dash
.
- It can handle multiple single options like
-vf filename
in the typical Unix way, automatically.
The disadvantage of getopts
is that it can only handle short options (-h
, not --help
) without additional code.
There is a getopts tutorial which explains what all of the syntax and variables mean. In bash, there is also help getopts
, which might be informative.
You can specify formal arguments in rake by adding symbol arguments to the task call. For example:
require 'rake'
task :my_task, [:arg1, :arg2] do |t, args|
puts "Args were: #{args} of class #{args.class}"
puts "arg1 was: '#{args[:arg1]}' of class #{args[:arg1].class}"
puts "arg2 was: '#{args[:arg2]}' of class #{args[:arg2].class}"
end
task :invoke_my_task do
Rake.application.invoke_task("my_task[1, 2]")
end
# or if you prefer this syntax...
task :invoke_my_task_2 do
Rake::Task[:my_task].invoke(3, 4)
end
# a task with prerequisites passes its
# arguments to it prerequisites
task :with_prerequisite, [:arg1, :arg2] => :my_task #<- name of prerequisite task
# to specify default values,
# we take advantage of args being a Rake::TaskArguments object
task :with_defaults, :arg1, :arg2 do |t, args|
args.with_defaults(:arg1 => :default_1, :arg2 => :default_2)
puts "Args with defaults were: #{args}"
end
Then, from the command line:
> rake my_task[1,false]
Args were: {:arg1=>"1", :arg2=>"false"} of class Rake::TaskArguments
arg1 was: '1' of class String
arg2 was: 'false' of class String
> rake "my_task[1, 2]"
Args were: {:arg1=>"1", :arg2=>"2"}
> rake invoke_my_task
Args were: {:arg1=>"1", :arg2=>"2"}
> rake invoke_my_task_2
Args were: {:arg1=>3, :arg2=>4}
> rake with_prerequisite[5,6]
Args were: {:arg1=>"5", :arg2=>"6"}
> rake with_defaults
Args with defaults were: {:arg1=>:default_1, :arg2=>:default_2}
> rake with_defaults['x','y']
Args with defaults were: {:arg1=>"x", :arg2=>"y"}
As demonstrated in the second example, if you want to use spaces, the quotes around the target name are necessary to keep the shell from splitting up the arguments at the space.
Looking at the code in rake.rb, it appears that rake does not parse task strings to extract arguments for prerequisites, so you can't do task :t1 => "dep[1,2]"
. The only way to specify different arguments for a prerequisite would be to invoke it explicitly within the dependent task action, as in :invoke_my_task
and :invoke_my_task_2
.
Note that some shells (like zsh) require you to escape the brackets: rake my_task\['arg1'\]
Best Answer
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).