This question already has answers here:
How do I parse command line arguments in Bash?
(40 answers)
Closed 6 years ago.
I need to write a bash script, and would like it to parse unordered parameters of the format:
scriptname --param1 <string> --param2 <string> --param3 <date>
Is there a simple way to accomplish this, or am I pretty much stuck with $1, $2, $3?
You want getopts.
while [[ $1 = -* ]]; do
arg=$1; shift # shift the found arg away.
case $arg in
--foo)
do_foo "$1"
shift # foo takes an arg, needs an extra shift
;;
--bar)
do_bar # bar takes no arg, doesn't need an extra shift
;;
esac
done
A nice example of how to implement short & long switches side by side is mcurl:
http://www.goforlinux.de/scripts/mcurl/
Bash has a getops function, as mentioned here before, that might solve your problems.
If you need anything more sophisticated, bash also supports positional parameters (ordered $1 ... $9, and then ${10} .... ${n}), you'll have to come up with your own logic to handle this input. One easy way to go is to put a switch/case inside of a for loop, iterating over the parameters. You can use either one of the two special bash vars that handle the input: $* or $#.
#!/bin/bash
# Parse the command-line arguments
while [ "$#" -gt "0" ]; do
case "$1" in
-p1|--param1)
PARAM1="$2"
shift 2
;;
-p2|--param2)
PARAM2="$2"
shift 2
;;
-p3|--param3)
PARAM3="$2"
shift 2
;;
-*|--*)
# Unknown option found
echo "Unknown option $1."
exit 1
;;
*)
CMD="$1"
break
;;
esac
done
echo "param1: $PARAM1, param2: $PARAM2, param3: $PARAM3, cmd: $CMD"
When I execute this:
./<my-script> --param2 my-param-2 --param1 myparam1 --param3 param-3 my-command
it outputs what you expect:
param1: myparam1, param2: my-param-2, param3: param-3, cmd: my-command
Related
I am trying to understand a piece of bash script that uses getopts.
I have
#!/bin/bash
function Argparser () {
while getopts 'o:dfath' arg "$#"; do
case $arg in
'o')
echo "oooooh"
output_dir=${OPTARG}
;;
'd')
echo 'ddddddd'
use_data_calib=true
;;
?)
echo "UNKNWON ARGS: ${OPTARG} "
exit 1
;;
esac
done
}
#----------------------------------------
# user parameters
#----------------------------------------
# data directory(required)
data_root_dir=$1
# output directory
output_dir=${data_root_dir}/videos
declare others=${#:2}
Argparser ${others}
#declare use_data_calib=false #<<-----HERE
echo ${output_dir}
echo ${data_root_dir}
echo ${others}
echo ${use_data_calib}
First I would like to understand what dfath does, and what arguments does this expect by using o:dfath.
How should I call this script and with what options?
Another thing that called my attention was the commented line (HERE) . I commented it and now I can set use_data_calib to true or false. However in the original code I am reading the line was not commented.
Doesn't that line defeat the purpose of the argument d? Because with that line, use_data_calib is always false...
o:dfath is just a string that refers to what options the script takes.
The letters are all of the options it can take, as in, -o, -d, etc.
Letters to the left of the colon indicate options which expect an argument, like -o myargument. Letters to the right of the colon do not expect an argument.
To your second point, I believe you are correct. The script you are working with is likely incomplete or incorrect.
I have a bash script I wrote with say, 3 command line options, bib, bob and boo... and I want to read in the user options into a bash variable of the same name, which I do as follows:
PARSED_OPTIONS=$(getopt -n $0 --long "bib:,bob:,boo:" -- "$#")
eval set -- "$PARSED_OPTIONS";
while true; do
case "$1" in
--bib)
bib=$2
shift 2;;
--bob)
bob=$2
shift 2;;
--boo)
boo=$2
shift 2 ;;
--)
shift
break;;
esac
done
This all works fine, so far, so good...
But now I want to extend this to a list of many many options, and so rather than writing out a long case statement, it would be really nice to be able to somehow loop over a list of options and automatically pass the options to the variable, something along these lines
opts="bib:,bob:,boo:,"
PARSED_OPTIONS=$(getopt -n $0 --long $opts -- "$#")
for arg in `echo $opts | tr , " "` ; do
eval set -- "$PARSED_OPTIONS";
while true; do
case "$1" in
--${arg})
declare $arg=$2
shift 2
;;
--)
shift
break;;
esac
done
done
I'm using the declaration statement to get the argument into a dynamic variable of the same name (see Dynamic variable names in Bash second solution), and this solution to do the loop over comma separated lists Loop through a comma-separated shell variable but I'm getting an infinite loop here. I think because the 2 unused options are allows as they are in the PARSED_OPTIONS list, but then they are not sliced off in the loop as only "arg" is looked for... I can't see an obvious way around this, but I'm sure there is one.
I realized that I had the shift command still inside the case statement, so that is why it wasn't exiting. I also needed to strip the colon : from the argument, so here is my automated argument retrieval for a bash script that works:
# specify an arbitrary list of arguments:
opts=bib:,bob:,boo:
PARSED_OPTIONS=$(getopt -n $0 --long "${opts}" -- "$#")
for arg in ${opts//,/ } ; do
var=${arg//:} # remove the colon
eval set -- "$PARSED_OPTIONS";
while true ; do
case "$1" in
--${var})
declare ${var}=$2
;;
--)
break
;;
esac
shift 2
done
done
So if you try test_script --boo 3 --bib hello --bob lkkfrfrfr
echo $bib $bob $boo
should give
hello lkkfrfrfr 3
Wrapping another command's parameters
I have a command tool1 that parses arguments this way:
#!/usr/bin/env bash
# ...
while [[ $# -ge 1 ]]
do
key="$1"
case $key in
-o|--option)
OPT="$2"
shift
;;
-u|--user)
USR="$2"
shift
;;
-*)
echo -e "Unrecognized option: \"$key\"" && exit 1
;;
*)
OTHERS+=("$1")
;;
esac
shift
done
# ...
I have tool2 that calls tool1. Thus tool2 will have to pass parameters to tool1. It may also need to process the same parameters (--user)
tool2 looks like:
#!/usr/bin/env bash
# ...
while [[ $# -ge 1 ]]
do
key="$1"
case $key in
-O|--option2)
opt2="$2"
shift
;;
-u|--user)
USR="$2"
OTHERS+=("-u $2")
shift
;;
-*)
echo -e "Unrecognized option: \"$key\"" && exit 1
;;
*)
OTHERS+=("$1")
;;
esac
shift
done
## Call tool1 with other parameters to pass
bash tool1.sh ${OTHERS[#]}
# ...
To sum up
--option2 is an option used only by tool2.
--user is common to both tools, and may be used by tool2 too, before calling tool1.sh. Because of this, in this example --user has to be explicitly passed to tool1 thanks to the array OTHERS.
I'd like to know about possible and/or alternative ways of dealing with such parameter redundancies. A methodology that would help me wrapping another tool's expected parameters/options, without having to copy/paste the lines regarding the parsing of such redundant parameters/options.
tool2's approach is fine. However, you aren't setting OTHERS correctly.
-u|--user)
USR="$2"
OTHERS+=("-u" "$2")
shift
-u and its argument need to remain separate array elements, just as they were separate arguments to tool2. You also need to quote the expansion of OTHERS, to preserve arguments containing word-splitting characters or globs:
bash tool1.sh "${OTHERS[#]}"
Finally, all-uppercase variable names are reserved for use by the shell itself; don't define such names yourself. Just use others instead of OTHERS.
So I've been reading around about getopts, getopt, etc. but I haven't found an exact solution to my problem.
The basic idea of the usage of my script is:
./program [-u] [-s] [-d] <TEXT>
Except TEXT is not required if -d is passed. Note that TEXT is usually a paragraph of text.
My main problem is that once getopts finishing parsing the flags, I have no way of knowing the position of the TEXT parameter. I could just assume that TEXT is the last argument, however, if a user messes up and does something like:
./program -u "sentence 1" "sentence 2"
then the program will not realize that the usage is incorrect.
The closest I've come is using getopt and IFS by doing
ARGS=$(getopt usd: $*)
IFS=' ' read -a array <<< "$ARGS"
The only problem is that TEXT might be a long paragraph of text and this method splits every word of text because of the spaces.
I'm thinking my best bet is to use a regular expression to ensure the usage is correctly formed and then extract the arguments with getopts, but it would be nice if there was a simpler solution
It's quite simple with getopts:
#!/bin/bash
u_set=0
s_set=0
d_set=0
while getopts usd OPT; do
case "$OPT" in
u) u_set=1;;
s) s_set=1;;
d) d_set=1;;
*) # getopts produces error
exit 1;;
esac
done
if ((!d_set && OPTIND>$#)); then
echo You must provide text or use -d >>/dev/stderr
exit 1
fi
# The easiest way to get rid of the processed options:
shift $((OPTIND-1))
# This will run all of the remaining arguments together with spaces between them:
TEXT="$*"
This is what I typically do:
local badflag=""
local aflag=""
local bflag=""
local cflag=""
local dflag=""
while [[ "$1" == -* ]]; do
case $1 in
-a)
aflag="-a"
;;
-b)
bflag="-b"
;;
-c)
cflag="-c"
;;
-d)
dflag="-d"
;;
*)
badflag=$1
;;
esac
shift
done
if [ "$badflag" != "" ]; do
echo "ERROR CONDITION"
fi
if [ "$1" == "" ] && [ "$dflag" == "" ]; do
echo "ERROR CONDITION"
fi
local remaining_text=$#
With first 9 arguments being referred from $1-$9, $10 gets interpreted as $1 followed by a 0. How do I account for this and access arguments to functions greater than 10?
Thanks.
Use :
#!/bin/bash
echo ${10}
To test the difference with $10, code in foo.sh :
#!/bin/bash
echo $10
echo ${10}
Then :
$ ./foo.sh first 2 3 4 5 6 7 8 9 10
first0
10
the same thing is true if you have :
foobar=42
foo=FOO
echo $foobar # echoes 42
echo ${foo}bar # echoes FOObar
Use {} when you want to remove ambiguities ...
my2c
If you are using bash, then you can use ${10}.
${...} syntax seems to be POSIX-compliant in this particular case, but it might be preferable to use the command shift like that :
while [ "$*" != "" ]; do
echo "Arg: $1"
shift
done
EDIT: I noticed I didn't explain what shift does. It just shift the arguments of the script (or function). Example:
> cat script.sh
echo "$1"
shift
echo "$1"
> ./script.sh "first arg" "second arg"
first arg
second arg
In case it can help, here is an example with getopt/shift :
while getopts a:bc OPT; do
case "$OPT" in
'a')
ADD=1
ADD_OPT="$OPTARG"
;;
'b')
BULK=1
;;
'c')
CHECK=1
;;
esac
done
shift $( expr $OPTIND - 1 )
FILE="$1"
In general, to be safe that the whole of a given string is used for the variable name when Bash is interpreting the code, you need to enclose it in braces: ${10}