How do I parse command line arguments in Bash?
Solution 1
Bash Space-Separated (e.g., --option argument
)
cat >/tmp/demo-space-separated.sh <<'EOF'
#!/bin/bash
POSITIONAL_ARGS=()
while [[ $# -gt 0 ]]; do
case $1 in
-e|--extension)
EXTENSION="$2"
shift # past argument
shift # past value
;;
-s|--searchpath)
SEARCHPATH="$2"
shift # past argument
shift # past value
;;
--default)
DEFAULT=YES
shift # past argument
;;
-*|--*)
echo "Unknown option $1"
exit 1
;;
*)
POSITIONAL_ARGS+=("$1") # save positional arg
shift # past argument
;;
esac
done
set -- "${POSITIONAL_ARGS[@]}" # restore positional parameters
echo "FILE EXTENSION = ${EXTENSION}"
echo "SEARCH PATH = ${SEARCHPATH}"
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 /etc/hosts
Output from copy-pasting the block above
FILE EXTENSION = conf
SEARCH PATH = /etc
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 /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
;;
--default)
DEFAULT=YES
shift # past argument with no value
;;
-*|--*)
echo "Unknown option $i"
exit 1
;;
*)
;;
esac
done
echo "FILE EXTENSION = ${EXTENSION}"
echo "SEARCH PATH = ${SEARCHPATH}"
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 /etc/hosts
Output from copy-pasting the block above
FILE EXTENSION = conf
SEARCH PATH = /etc
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 /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.
Solution 2
No answer showcases enhanced getopt. And the top-voted answer is misleading: It either ignores -vfd
style short options (requested by the OP) or options after positional arguments (also requested by the OP); and it ignores parsing-errors. Instead:
-
Use enhanced
getopt
from util-linux or formerly GNU glibc.1 - It works with
getopt_long()
the C function of GNU glibc. -
no other solution on this page can do all this:
- handles spaces, quoting characters and even binary in arguments2 (non-enhanced
getopt
can’t do this) - it can handle options at the end:
script.sh -o outFile file1 file2 -v
(getopts
doesn’t do this) - allows
=
-style long options:script.sh --outfile=fileOut --infile fileIn
(allowing both is lengthy if self parsing) - allows combined short options, e.g.
-vfd
(real work if self parsing) - allows touching option-arguments, e.g.
-oOutfile
or-vfdoOutfile
- handles spaces, quoting characters and even binary in arguments2 (non-enhanced
- Is so old already3 that no GNU system is missing this (e.g. any Linux has it).
- You can test for its existence with:
getopt --test
→ return value 4. - Other
getopt
or shell-builtingetopts
are of limited use.
The following calls
myscript -vfd ./foo/bar/someFile -o /fizz/someOtherFile
myscript -v -f -d -o/fizz/someOtherFile -- ./foo/bar/someFile
myscript --verbose --force --debug ./foo/bar/someFile -o/fizz/someOtherFile
myscript --output=/fizz/someOtherFile ./foo/bar/someFile -vfd
myscript ./foo/bar/someFile -df -v --output /fizz/someOtherFile
all return
verbose: y, force: y, debug: y, in: ./foo/bar/someFile, out: /fizz/someOtherFile
with the following myscript
#!/bin/bash
# More safety, by turning some bugs into errors.
# Without `errexit` you don’t need ! and can replace
# PIPESTATUS with a simple $?, but I don’t do that.
set -o errexit -o pipefail -o noclobber -o nounset
# -allow a command to fail with !’s side effect on errexit
# -use return value from ${PIPESTATUS[0]}, because ! hosed $?
! getopt --test > /dev/null
if [[ ${PIPESTATUS[0]} -ne 4 ]]; then
echo 'I’m sorry, `getopt --test` failed in this environment.'
exit 1
fi
OPTIONS=dfo:v
LONGOPTS=debug,force,output:,verbose
# -regarding ! and PIPESTATUS see above
# -temporarily store output to be able to check for errors
# -activate quoting/enhanced mode (e.g. by writing out “--options”)
# -pass arguments only via -- "$@" to separate them correctly
! PARSED=$(getopt --options=$OPTIONS --longoptions=$LONGOPTS --name "$0" -- "$@")
if [[ ${PIPESTATUS[0]} -ne 0 ]]; then
# e.g. return value is 1
# then getopt has complained about wrong arguments to stdout
exit 2
fi
# read getopt’s output this way to handle the quoting right:
eval set -- "$PARSED"
d=n f=n v=n outFile=-
# now enjoy the options in order and nicely split until we see --
while true; do
case "$1" in
-d|--debug)
d=y
shift
;;
-f|--force)
f=y
shift
;;
-v|--verbose)
v=y
shift
;;
-o|--output)
outFile="$2"
shift 2
;;
--)
shift
break
;;
*)
echo "Programming error"
exit 3
;;
esac
done
# handle non-option arguments
if [[ $# -ne 1 ]]; then
echo "$0: A single input file is required."
exit 4
fi
echo "verbose: $v, force: $f, debug: $d, in: $1, out: $outFile"
1 enhanced getopt is available on most “bash-systems”, including Cygwin; on OS X try brew install gnu-getopt or sudo port install getopt
2 the POSIX exec()
conventions have no reliable way to pass binary NULL in command line arguments; those bytes prematurely end the argument
3 first version released in 1997 or before (I only tracked it back to 1997)
Solution 3
deploy.sh
#!/bin/bash
while [[ "$#" -gt 0 ]]; do
case $1 in
-t|--target) target="$2"; shift ;;
-u|--uglify) uglify=1 ;;
*) echo "Unknown parameter passed: $1"; exit 1 ;;
esac
shift
done
echo "Where to deploy: $target"
echo "Should uglify : $uglify"
Usage:
./deploy.sh -t dev -u
# OR:
./deploy.sh --target dev --uglify
Solution 4
From digitalpeer.com with minor modifications:
Usage myscript.sh -p=my_prefix -s=dirname -l=libname
#!/bin/bash
for i in "$@"
do
case $i in
-p=*|--prefix=*)
PREFIX="${i#*=}"
;;
-s=*|--searchpath=*)
SEARCHPATH="${i#*=}"
;;
-l=*|--lib=*)
DIR="${i#*=}"
;;
--default)
DEFAULT=YES
;;
*)
# unknown option
;;
esac
done
echo PREFIX = ${PREFIX}
echo SEARCH PATH = ${SEARCHPATH}
echo DIRS = ${DIR}
echo DEFAULT = ${DEFAULT}
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.
Solution 5
while [ "$#" -gt 0 ]; do
case "$1" in
-n) name="$2"; shift 2;;
-p) pidfile="$2"; shift 2;;
-l) logfile="$2"; shift 2;;
--name=*) name="${1#*=}"; shift 1;;
--pidfile=*) pidfile="${1#*=}"; shift 1;;
--logfile=*) logfile="${1#*=}"; shift 1;;
--name|--pidfile|--logfile) echo "$1 requires an argument" >&2; exit 1;;
-*) echo "unknown option: $1" >&2; exit 1;;
*) handle_argument "$1"; shift 1;;
esac
done
This solution:
- handles
-n arg
and--name=arg
- allows arguments at the end
- shows sane errors if anything is misspelled
- compatible, doesn't use bashisms
- readable, doesn't require maintaining state in a loop
Redwood
I'm a San Francisco Bay Area programmer working in Objective-C/Cocoa and C#/.NET.
Updated on January 27, 2022Comments
-
Redwood over 2 years
Say, I have a script that gets called with this line:
./myscript -vfd ./foo/bar/someFile -o /fizz/someOtherFile
or this one:
./myscript -v -f -d -o /fizz/someOtherFile ./foo/bar/someFile
What's the accepted way of parsing this such that in each case (or some combination of the two)
$v
,$f
, and$d
will all be set totrue
and$outFile
will be equal to/fizz/someOtherFile
? -
Livven about 11 yearsIs this really true? According to Wikipedia there's a newer GNU enhanced version of
getopt
which includes all the functionality ofgetopts
and then some.man getopt
on Ubuntu 13.04 outputsgetopt - parse command options (enhanced)
as the name, so I presume this enhanced version is standard now. -
szablica almost 11 yearsThat something is a certain way on your system is a very weak premise to base asumptions of "being standard" on.
-
Tobias Kienzler over 10 yearsNeat! Though this won't work for space-separated arguments à la
mount -t tempfs ...
. One can probably fix this via something likewhile [ $# -ge 1 ]; do param=$1; shift; case $param in; -p) prefix=$1; shift;;
etc -
Stephane Chazelas almost 10 years@Livven, that
getopt
is not a GNU utility, it's part ofutil-linux
. -
m3nda about 9 yearsI read all and this one is my preferred one. I don't like to use
-a=1
as argc style. I prefer to put first the main option -options and later the special ones with single spacing-o option
. Im looking for the simplest-vs-better way to read argvs. -
m3nda about 9 yearsIt's working really well but if you pass an argument to a non a: option all the following options would be taken as arguments. You can check this line
./myscript -v -d fail -o /fizz/someOtherFile -f ./foo/bar/someFile
with your own script. -d option is not set as d: -
bronson over 8 yearsSorry for the delay. In my script, the handle_argument function receives all the non-option arguments. You can replace that line with whatever you'd like, maybe
*) die "unrecognized argument: $1"
or collect the args into a variable*) args+="$1"; shift 1;;
. -
radu.ciorba over 8 yearsi used this with a small modification to append all unrecognized options to an array that then becomes our new $@ gist.github.com/rciorba/514fd75f4a6471d44d71
-
Robert Siemer over 8 yearsThis code can’t handle options with arguments like this:
-c1
. And the use of=
to separate short options from their arguments is unusual... -
Robert Siemer over 8 yearsThis can’t handle
-vfd
style combined short options. -
Robert Siemer about 8 yearsUsing
$*
is broken usage ofgetopt
. (It hoses arguments with spaces.) See my answer for proper usage. -
sfnd about 8 yearsI ran into two problems with this useful chunk of code: 1) the "shift" in the case of "-c=foo" ends up eating the next parameter; and 2) 'c' should not be included in the "[cfr]" pattern for combinable short options.
-
Nicolas Lacombe about 8 yearsIf you use
-gt 0
, remove yourshift
after theesac
, augment all theshift
by 1 and add this case:*) break;;
you can handle non optionnal arguments. Ex: pastebin.com/6DJ57HTc -
RichVel almost 8 yearsThanks for writing argbash, I just used it and found it works well. I mostly went for argbash because it's a code generator supporting the older bash 3.x found on OS X 10.11 El Capitan. The only downside is that the code-generator approach means quite a lot of code in your main script, compared to calling a module.
-
bubla almost 8 yearsYou can actually use Argbash in a way that it produces tailor-made parsing library just for you that you can have included in your script or you can have it in a separate file and just source it. I have added an example to demonstrate that and I have made it more explicit in the documentation, too.
-
RichVel almost 8 yearsGood to know. That example is interesting but still not really clear - maybe you can change name of the generated script to 'parse_lib.sh' or similar and show where the main script calls it (like in the wrapping script section which is more complex use case).
-
Oleksii Chekulaiev over 7 yearsTo use the demo to parse params that come into your bash script you just do
show_use "$@"
-
Oleksii Chekulaiev over 7 yearsBasically I found out that github.com/renatosilva/easyoptions does the same in the same way but is a bit more massive than this function.
-
Charles Duffy over 7 yearsgrumble re: using all-uppercase variable names, in contravention of POSIX convention specifying that upper-case variables are used for names with meaning to POSIX-specified tools, and lower-case names are reserved for application use. (This is relevant to shell variables as well, as a shell variable assignment will overwrite any like-named environment variable that exists).
-
Luca Davanzo over 7 yearswhat's the meaning for "+x" on ${key+x} ?
-
galmok over 7 yearsIt is a test to see if 'key' is present or not. Further down I unset key and this breaks the inner while loop.
-
bubla over 7 yearsThe issues were addressed in recent version of argbash: Documentation has been improved, a quickstart argbash-init script has been introduced and you can even use argbash online at argbash.io/generate
-
johncip over 7 yearsThanks for this. Just confirmed from the feature table at en.wikipedia.org/wiki/Getopts, if you need support for long options, and you're not on Solaris,
getopt
is the way to go. -
gcscaglia over 7 yearsWhile parsing arguments yourself using a while loop and a case is the right way to go, please don't use the example code of this answer. It has nasty problems, like silently ignoring unknown or malformed options and no support for trailing positional parameters (i.e. things after --)
-
Kaushal Modi about 7 yearsI believe that the only caveat with
getopt
is that it cannot be used conveniently in wrapper scripts where one might have few options specific to the wrapper script, and then pass the non-wrapper-script options to the wrapped executable, intact. Let's say I have agrep
wrapper calledmygrep
and I have an option--foo
specific tomygrep
, then I cannot domygrep --foo -A 2
, and have the-A 2
passed automatically togrep
; I need to domygrep --foo -- -A 2
. Here is my implementation on top of your solution. -
Tom N Tech about 7 yearsJust wanted to advocate for the recommended solution. After monkeying with several options, this is the most painless and simple.
-
mauron85 about 7 yearsLike this one. Maybe just add -e param to echo with new line.
-
Josh Wulf about 7 yearsI get this on Mac OS X: ``` lib/bashopts.sh: line 138: declare: -A: invalid option declare: usage: declare [-afFirtx] [-p] [name[=value] ...] Error in lib/bashopts.sh:138. 'declare -x -A bashopts_optprop_name' exited with status 2 Call tree: 1: lib/controller.sh:4 source(...) Exiting with status 1 ```
-
Josh Wulf about 7 yearsYou need Bash version 4 to use this. On Mac, the default version is 3. You can use home brew to install bash 4.
-
kolydart almost 7 yearsYou do not echo
–default
. In the first example, I notice that if–default
is the last argument, it is not processed (considered as non-opt), unlesswhile [[ $# -gt 1 ]]
is set aswhile [[ $# -gt 0 ]]
-
SDsolar almost 7 yearsWhy would you want to make it more complicated?
-
Bruno Bronosky almost 7 yearsI finally fixed the issue in the "Straight Bash Space Separated" example with
-gt 0
vs-gt 1
as described by @NicolasMongrain-Lacombe and @kolydart -
Simon A. Eugster over 6 yearsThe
getopts "h?vf:"
should begetopts "hvf:"
without question mark. Arguments which are not recognized are stored as?
in$opt
. Quote fromman builtins
:“The colon and question mark characters may not be used as option characters.”
-
Will Barnwell over 6 yearsThis takes the same approach as Noah's answer, but has less safety checks / safeguards. This allows us to write arbitrary arguments into the script's environment and I'm pretty sure your use of eval here may allow command injection.
-
Jason S over 6 yearsYou use shift on the known arguments and not on the unknown ones so your remaining
$@
will be all but the first two arguments (in the order they are passed in), which could lead to some mistakes if you try to use$@
later. You don't need the shift for the = parameters, since you're not handling spaces and you're getting the value with the substring removal#*=
-
Masadow over 6 yearsYou're right, in fact, since I build a PARAMS variable, I don't need to use shift at all
-
Jakub Kukul over 6 yearsIf you're running in the bash "strict mode" (
#!/bin/bash -u
orset -eu
), only restore positional parameters ifPOSITIONAL
array is not empty, to avoidunbound variable
error, i.e.:if [ ${#POSITIONAL[@]} -gt 0 ]; then set -- "${POSITIONAL[@]}" ; fi # restore positional parameters
-
lionello over 6 yearsFirst sample doesn't appear to handle positional arguments with spaces:
test.sh "a b"
will end up having$1
set to "a" and$2
set to "b". -
bobpaul over 6 yearsI like this answer. I find the statement from "wooledge.org" that using enhanced getopt means you'll do twice as much work rather misleading. Most of the scripts I write for me. They need to run on my system. I never give them to anyone else (except maybe others at work, if it's that sort of script). At most I need to test to ensure enhanced getopt is installed and then exit with a message "enhanced getopt is required to run this script". Easy peasy. That said, I'm not sure I've ever seen enhanced getopt installed by default. It's part of linux-util, which is a dev package on most distros.
-
Robert Siemer over 6 years@bobpaul Your statement about util-linux is wrong and misleading as well: the package is marked “essential” on Ubuntu/Debian. As such, it is always installed. – Which distros are you talking about (where you say it needs to be installed on purpose)?
-
hfossli about 6 yearsThis is what I am doing. Have to
while [[ "$#" > 1 ]]
if I want to support ending the line with a boolean flag./script.sh --debug dev --uglify fast --verbose
. Example: gist.github.com/hfossli/4368aa5a577742c3c9f9266ed214aa58 -
hfossli about 6 yearsWow! Simple and clean! This is how I'm using this: gist.github.com/hfossli/4368aa5a577742c3c9f9266ed214aa58
-
Guilherme Garnier about 6 yearsAmazing! I've tested a couple of answers, but this is the only one that worked for all cases, including many positional parameters (both before and after flags)
-
thebunnyrules about 6 years@Matt J, the first part of the script (for i) would be able to handle arguments with spaces in them if you use "$i" instead of $i. The getopts does not seem to be able to handle arguments with spaces. What would be the advantage of using getopt over the for i loop?
-
Benjamin W. almost 6 yearsI don't know about the timelines of the different answers, but the top answer does cover
-vfd
style short options by way of thegetopts
built-in. -
Martynas Jusevičius almost 6 yearsHow can I process a portion of the arguments recognized by the current script, and pass the rest to another script using
$@
?$@
is empty after arguments are parsed as shown in the answer. -
Bruno Bronosky almost 6 years@MartynasJusevičius the
shift
commands takes them or of$@
so limit the number of times you shift. -
Martynas Jusevičius almost 6 yearsSo I would need to collect unknown options into my own array instead of
$@
? I thought$POSITIONAL
was doing something similar, but looks like it only stores keys, not values. What is the purpose of it? -
Martynas Jusevičius almost 6 yearsActually, if the
+=("$1")
syntax to append to an array, it's not working for me. I only get the very first option when I print$POSITIONAL
. Edit: nevermind,${POSITIONAL[@]}
works :) Thanks. -
pappix over 5 yearswhen using "shift" in a loop, I would advise to do something like:
shift || exit 1
This is because if shift misbehaves and fails for some reason to shift the arguments, you might get caught in an infinite loop. This way instead if shift has a return value different from 0 you can safely exit and report an error has occurred. -
Manish Kumar over 5 years
Bash Equals-Separated (e.g., --option=argument) (without getopt[s])
what changes i will have to do for-x y
instead of-x=y
? -
RealHandy over 5 yearsThis is much nicer to paste into each script rather than dealing with source or having people wonder where your functionality actually starts.
-
jjj about 5 yearsNote this doesn't work on Mac at least up to the current 10.14.3. The getopt that ships is BSD getopt from 1999...
-
Robert Siemer about 5 years@jjj footnote 1 covers OS X. – For OS X out-of-the-box solution check other questions and answers. Or to be honest: for real programming don’t use bash. ;-)
-
Harlin about 5 yearsThis is a very good method to do this (without getopts) if you know you can use bash. For me, that's close to 99% of the time. I use this with most of my intermediate-level scripts in my own environment or as helper scripts in my own software.
-
lauksas about 5 yearsnice succinct code, but using -n and no other arg causes infinite loop due to error on
shift 2
, issuingshift
twice instead ofshift 2
. Suggested the edit. -
Robert Siemer about 5 years@BenjaminW. The top-voted answer covers two self parsing solutions and getopts. The former don’t do combined short options, the latter doesn’t parse options after non-option arguments.
-
thibmaek about 5 yearsIn this script,
ease
is incorrect and should be replaced withesac
to close the case block -
Tyrel Kostyk about 5 yearsIn the
Bash Equals-Separated (e.g., --option=argument) (without getopt[s])
method, why is theshift
built-in needed? Shouldn't thefor
loop iterate through each option automatically? If you used the supplied example as-is but just removed theshift
commands, would it not be better as that way you don't discard your inputs? -
Bruno Bronosky about 5 years@TyrelKostyk if you actually try your suggestion, you'll see that the shift is what makes
$1
point to the first non-option argument/etc/hosts
. If you don't shift, you'll have to increment your own((counter++))
in the loop and then call${!counter}
to get the "variable variable". If that's better for you, do it. But I think that's needless complexity to avoid discarding inputs that are stored as well named variables. -
Tyrel Kostyk about 5 years@BrunoBronosky Thanks for the reply! Makes sense. I discovered something from my confusion: I was applying your answer in the form of a "parse_inputs" function. And I found that when using your exact implementation, and calling
parse_inputs "$@"
, theshift
operator has no effect, and whether you use it or not, it doesn't discard your inputs. (Should this be added onto your post as an edit?) -
Jay Somedon almost 5 yearsIs there link to doc on
getopt
command? All I see after googlinggetopt
is about the c funciton not the terminal command.. -
Robert Siemer almost 5 years@JaySomedon
man getopt
works on the command line and pretty good with Google. Go for section 1, i.e. getopt(1), because getopt(3) is the C function you talk about. -
yair almost 5 yearsWarning: this tolerates duplicated arguments, the latest argument prevails. e.g.
./script.sh -d dev -d prod
would result indeploy == 'prod'
. I used it anyway :P :) :+1: -
transang over 4 yearsWhat does the leading exclaimation mark mean? Could anyone add some reference?
-
Robert Siemer over 4 years@transang Boolean negation of the return value. And its side effect: allow a command to fail (otherwise errexit would abort the program on error). -- The comments in the script tell you more. Otherwise:
man bash
-
EM0 over 4 yearsI'm using this (thanks!) but note that it allows empty argument value, e.g.
./script.sh -d
would not generate an error but just set$deploy
to an empty string. -
Freedo over 4 yearsI'm using your first method and it doesn't work if the value has spaces on it
-
lxop over 4 years@TyrelKostyk your problem is caused by the function getting its own arguments array. So when you
shift
inside the function, it drops elements from the function's argument array, not that of the overall script. A solution might be to build apositional
array in your function, then useset -- ${positional[@]}
immediately after calling the parse function. -
Mr. Polywhirl over 4 yearsThis is one of the best answers.
-
chenrici about 4 yearsI downvoted , because i believe the below answer is the correct answer.
-
leogama about 4 yearsI've made an edit (it's waiting for review) to add some useful features while keeping the code straightforward and small. For fancier features, like multiple one letter options in a single argument, you should try
getopt
orgetopts
. -
simplename over 3 yearswhat is this line doing
cat >/tmp/demo-getopts.sh <<'EOF'
-
Robert Siemer over 3 years@einpoklum There are two
set
and I believe you mean the first: I disagree. I see your point, but I do not wish to provide an answer which does not work in the environment I consider “best practice”. -
Jaraws over 3 yearsIn bash space separated, what does this expression means: POSITIONAL=()??
-
Enissay over 3 yearsGood smart way to do it. I am using it from now on until a better way or a bug is found maybe ;-)
-
CIsForCookies over 3 yearsGreat answer, tnx! I shortened it a bit -
while (( "$#" )); do
instead ofwhile [[ "$#" -gt 0 ]]; do
-
Inanc Gumus over 3 years@CIsForCookies Thx! That's because, afaik,
((...))
syntax is only exists in ksh, bash, and zsh, but not in plain sh. -
user8162 about 3 yearsIf you want to generically evaluate
--option
and-option
without repeatingOPTION=$i
every time, use-*=*)
as match pattern andeval ${i##*-}
. -
Lukas S. about 3 yearsThis looks great - but wondering if
END_OF_OPT=1
is actually necessary on this line:--*) ARGV+=("$arg"); END_OF_OPT=1 ;;
. If left in there, it fails to parse--username=fred
if it's included after--quiet
(or any other long-style boolean option). For example,script.sh --quiet --username=fred
fails withUnrecognized argument: --username=fred
(thoughscript.sh --quiet --username fred
works fine). I took out thatEND_OF_OPT=1
in my script and now it works, but not sure if maybe that breaks some other scenario I'm not aware of. -
von spotz about 3 years@BrunoBronosky could you please elaborate what the line
cat >/tmp/demo-space-separated.sh <<'EOF'
is doing and why you put anEOF
at the end of the file? -
Bruno Bronosky about 3 years@vonspotz Notice that the after each code sample block, there is a block labeled "Output from copy-pasting the block above:" That is because the previous block is written in a way that can be copy-pasted into your [bash] shell, as-is, no modification. That first line says
cat
, redirectingstdout
intoblah.sh
. Sincecat
is not given a file to read, it tries to readstdin
. I use a bash here-doc to stream a string of text in. wrapping the delimiter (which could be anything but I choseEOL
) in single quotes prevent bash from performing expansions within the string. -
madhukar93 about 3 yearsworks well for my usecase of mixing positional and optional args in any order, thanks.
-
Samuel Åslund about 3 yearsIf you want/need long-opts with this portable getopts this query have a nice solution: stackoverflow.com/q/35235707/671282
-
YvesLeBorg almost 3 years@jjj brew install gnu-getopt ... and dont forget the small print about your path.
-
Kvothe almost 3 yearsI want to allow options to take multiple arguments, an unknown number a priori. Can I use something based on this for that purpose? So
script -opt1 1 2 3 4 -opt2 5 6 7
should allow me to refer to elements of the set1 2 3 4
and of5 6 7
in the script. -
F. Hauri - Give Up GitHub over 2 yearsWrong! Don't work if invoked as
./deploy.sh -ut dev
!! See Robert Siemers's answer -
vaeVictis over 2 years@RobertSiemer I just broke your 666 upvotes record, with my upvote :-D Very well explained. Thanks.
-
Gabriel Staples over 2 years
shift
removes arguments from the input arguments array,"$@"
. If you get into a situation where after parsing you still need to restore that array to re-use all of the input arguments somewhere else (not just the positional parameters via thePOSITIONAL_ARGS
array as shown in this answer), it can be done by backing up the full input arguments array and using theset
command later, as shown here. -
Gabriel Staples over 2 yearsIf anyone is looking to see advanced argument parsing from this answer in a full, advanced bash program with help menu, argument parsing, main function, automatic execute vs source detection (akin to
if __name__ == "__main__":
in Python), etc, see my demo/template program in this list here. It is currently calledargument_parsing__3_advanced__gen_prog_template.sh
, but if that name changes in the future I'll update it in the list at the link just above. -
pmarreck over 2 yearsIn your example, don't you actually have to shift twice if you pass a flag that has a parameter? I like the cleanliness of this otherwise, even if it won't accept "=" between an option and value.
-
Inanc Gumus over 2 yearsYes,
target
makes another shift—butuglify
makes a single one. -
Inanc Gumus over 2 years@F.Hauri It's not wrong, please be kind. This follows Go's way of handling parameters. For a more complex scenario, yes, take a look at that answer as well.
-
F. Hauri - Give Up GitHub over 2 years@InancGumus This doesn't follow POSIX recommendations!
deploy -ut dev
will answerUnknown parameter passed: -ut
, It's wrong! ... and shell is not go! -
Inanc Gumus over 2 yearsYou could have said: It's not a POSIX compliant way instead of rudely saying "wrong" with a bold style (OMG). It's not wrong as long as it works for some people (including me).
-
cdgraham over 2 years@yair That's how CLI arguments usually work as far as I've seen. It's nice because you can override options. For example I can
alias build='./script.sh -a 1 -b 2 -c 3'
for my default options, thenbuild -b 6
to override individuals! -
Liso about 2 years
-
leogama about 2 yearsThank you, @Liso! I need to update this answer. I've analyzed the regular expressions' decision tree and found some minor errors (nothing serious though).
-
Liso about 2 years@leogama Yeah, I've used this code in my script, works great overall ! Keep up the good work 👍👍
-
taiyodayo about 2 yearson MacOS 10.16, even after
brew install gnu-getopt && brew link --force gnu-getopt
,getopt --test
doesn't return the value 4. I wonder why? (which getopt
is correctly linked to/usr/local/opt/gnu-getopt/bin/getopt
) Original BSD getopt returns--
. -
Robert Siemer about 2 years@taiyodayo How do you test for the return value? What does
getopt --version
say? -
midnite about 2 years
getopt
is POSIX too. -
KasRoudra about 2 yearsI have a question here. Why did you use
shift; OUTPUTFILE="$1"
instead ofOUTPUTFILE="$2"
? Maybe it has an easy answer but I am a newbie in bash -
Ponyboy47 about 2 yearsI believe you could do either and it really just comes down to personal preference. In this case I just wanted to keep
$1
as the "active" argument everywhere