Using getopt to parse second argument into a variable - bash

How do I parse the second argument into a variable using bash and getopt on the following script.
I can do sh test.sh -u and get "userENT" to display. But if I do sh test.sh -u testuser on this script I get an error.
#!/bin/sh
# Now check for arguments
OPTS=`getopt -o upbdhrstv: --long username,password,docker-build,help,version,\
release,remote-registry,stage,develop,target: -n 'parse-options' -- "$#"`
while true; do
case "$1" in
-u | --username)
case "$2" in
*) API_KEY_ART_USER="$2"; echo "userENT" ;shift ;;
esac ;;
-- ) shift; break ;;
* ) if [ _$1 != "_" ]; then ERROR=true; echo; echo "Invalid option $1"; fi; break ;;
esac
done
echo "user" $API_KEY_ART_USER
How can I pass the -u testuser and not have an Invalid option testuser error?
Output:
>sh test3.sh -u testuser
userENT
Invalid option testuser
user testuser

man getopt would tell you that a colon following the option indicates that it has an argument. You only have a colon after the v. You also weren't shifting within your loop, so you'd be unable to parse any options past the first one. And I'm not sure why you felt the need to have a second case statement that only had a single default option. In addition, there were a number of poor practices in your code including use of all caps variable names and backticks instead of $() for executing commands. And you've tagged your question bash but your shebang is /bin/sh. Give this a try, but you shouldn't be using code without understanding what it does.
#!/bin/sh
# Now check for arguments
opts=$(getopt -o u:pbdhrstv: --long username:,password,docker-build,help,version,\
release,remote-registry,stage,develop,target: -n 'parse-options' -- "$#")
while true; do
case "$1" in
-u|--username)
shift
api_key_art_user="$1"
echo "userENT"
;;
--)
shift;
break
;;
*)
if [ -n "$1" ]; then
err=true
echo "Invalid option $1"
fi
break
;;
esac
shift
done
echo "user $api_key_art_user"

Related

Is there a way in bash script to have an option to give an argument but it shouldn't a must?

I have a scenario where i would like to assign an option a default value but a user can decide to give it another argument:
Here is an example
check_param() {
for arg in "$#"; do
shift
case "$arg" in
"--force") set -- "$#" "-f" ;;
"--type") set -- "$#" "-t" ;;
"--help") set -- "$#" "-h" ;;
"--"*) echo "Unknown parameter: " $arg; show_help; exit 1 ;;
*) set -- "$#" "$arg"
esac
done
# Standard Variables
force=0
type="daily"
OPTIND=1
while getopts "hft:v" opt
do
case "$opt" in
"f") force=1 ;;
"t") type=${OPTARG} ;;
"h") show_help; exit 0 ;;
"?") show_help; exit 1 ;;
esac
done
shift $(expr $OPTIND - 1) # remove options from positional parameters
From the above example, i would like when the user gives the parameter -t without any argument to apply the default value which is daily , and the user can also use parameter -t with any other argument and that will be checked later in code.
The problem is now the parameter -t must be given an argument due to the colon, but i kinda need for it to do both, with or without argument.
Thanks in advance for any explanations or links to any article that can help.
So according to a suggestion i got Here is the test result
check_param() {
## Standard Variablen der Parameter
force=0
type="daily.0"
## Break down the options in command lines for easy parsing
## -l is to accept the long options too
args=$(getopt -o hft::v -l force,type::,help -- "$#")
eval set -- "$args"
## Debugging mechanism
echo ${args}
echo "Number of parameters $#"
echo "first parameter $1"
echo "Second parameter $2"
echo "third parameter $3"
while (($#)); do
case "$1" in
-f|--force) force=1; ;;
-t|--type) type="${2:-${type}}"; shift; ;;
-h|--help) show_help; exit 0; ;;
--) shift; break; ;;
*) echo "Unbekannter Parameter"; exit 1; ;;
esac
shift
done
echo ${type}
}
check_param $#
echo ${type}
The output:
sh scriptsh -t patch.0
-t '' -- 'patch.0'
Number of parameters 4
first parameter -t
Second parameter
third parameter --
daily.0
daily.0
It still didn't assign the value patch to the variable type
Is there a way in bash script to have an option to give an argument but it shouldn't a must?
Yes, there is a way.
getopts does not supports optional arguments. So... you can:
roll your own bash library for parsing arguments or
use another tool that has support for optional arguments.
A common tool is getopt that should be available on any linux.
args=$(getopt -o hft::v -l force,type::,help -- "$#")
eval set -- "$args"
while (($#)); do
case "$1" in
-f|--force) force=1; ;;
-t|--type) type="${2:-default_value}"; shift; ;;
-h|--help) echo "THis is help"; exit; ;;
--) shift; break; ;;
*) echo "Error parsgin arguments"; exit 1; ;;
esac
shift
done
getopt handles long arguments and reorders arguments, so you can ./prog file1 -t opt and ./prog -t opt file1 with same result.

Is there a way to continue in a Flag when there is no $OPTARG set in Bash, GETOPTS?

I would like to build a script with getopts, that continues in the flag, when an $OPTARG isn't set.
My script looks like this:
OPTIONS=':dBhmtb:P:'
while getopts $OPTIONS OPTION
do
case "$OPTION" in
m ) echo "m"
t ) echo "t"
d ) echo "d";;
h ) echo "h";;
B ) echo "b";;
r ) echo "r";;
b ) echo "b"
P ) echo hi;;
#continue here
\? ) echo "?";;
:) echo "test -$OPTARG requieres an argument" >&2
esac
done
My aim is to continue at my comment, when there is no $OPTARG set for -P.
All I get after running ./test -P is :
test -P requieres an argument
and then it continues after the loop but I want to continue in the -P flag.
All clear?
Any Ideas?
First, fix the missing ;; in some of the case branches.
I don't think you can: you told getopts that -P requires an argument: two error cases
-P without an argument is the last option. In this case getops sees that nothing follows -P and sets the OPTION variable to :, which you handle in the case statement.
-P is followed by another option: getopts will simply take the next word, even if the next word is another option, as OPTARG.
Change the case branch to
P ) echo "P: '$OPTARG'";;
Then:
invoking the script like bash script.sh -P -m -t, the output is
P: '-m'
t
invoking the script like bash script.sh -Pmt, the output is
P: 'mt'
This is clearly difficult to work around. How do you know if the user intended the option argument to be literally "mt" and not the options -m and -t?
You might be able to work around this using getopt (see the canonical example) using an optional argument for a long option (those require an equal sign like --long=value) so it's maybe easier to check if the option argument is missing or not.
Translating getopts parsing to getopt -- it's more verbose, but you have finer-grained control
die() { echo "$*" >&2; exit 1; }
tmpArgs=$(getopt -o 'dBhmt' \
--long 'b::,P::' \
-n "$(basename "$0")" \
-- "$#"
)
(( $? == 0 )) || die 'Problem parsing options'
eval set -- "$tmpArgs"
while true; do
case "$1" in
-d) echo d; shift ;;
-B) echo B; shift ;;
-h) echo h; shift ;;
-m) echo m; shift ;;
-t) echo t; shift ;;
--P) case "$2" in
'') echo "P with no argument" ;;
*) echo "P: $2" ;;
esac
shift 2
;;
--b) case "$2" in
'') echo "b with no argument" ;;
*) echo "b: $2" ;;
esac
shift 2
;;
--) shift; break ;;
*) printf "> %q\n" "$#"
die 'getopt internal error: $*' ;;
esac
done
echo "Remaining arguments:"
for ((i=1; i<=$#; i++)); do
echo "$i: ${!i}"
done
Successfully invoking the program with --P:
$ ./myscript.sh --P -mt foo bar
P with no argument
m
t
Remaining arguments:
1: foo
2: bar
$ ./myscript.sh --P=arg -mt foo bar
P: arg
m
t
Remaining arguments:
1: foo
2: bar
This does impose higher overhead on your users, because -P (with one dash) is invalid, and the argument must be given with =
$ ./myscript.sh --P arg -mt foo bar
P with no argument
m
t
Remaining arguments:
1: arg
2: foo
3: bar
$ ./myscript.sh --Parg mt foo bar
myscript.sh: unrecognized option `--Parg'
Problem parsing options
$ ./myscript.sh -P -mt foo bar
myscript.sh: invalid option -- P
Problem parsing options
$ ./myscript.sh -P=arg -mt foo bar
myscript.sh: invalid option -- P
myscript.sh: invalid option -- =
myscript.sh: invalid option -- a
myscript.sh: invalid option -- r
myscript.sh: invalid option -- g
Problem parsing options
Do not mix logic with arguments parsing.
Prefer lower case variables.
My aim is to continue at my comment, when there is no $OPTARG set for -P
I advise not to. The less you do at one scope, the less you have to think about. Split parsing options and executing actions in separate stages. I advise to:
# set default values for options
do_something_related_to_P=false
recursive=false
tree_output=false
# parse arguments
while getopts ':dBhmtb:P:' option; do
case "$option" in
t) tree_output=true; ;;
r) recursive="$OPTARG"; ;;
P) do_something_related_to_P="$OPTARG"; ;;
\?) echo "?";;
:) echo "test -$OPTARG requieres an argument" >&2
esac
done
# application logic
if "$do_something_related_to_P"; then
do something related to P
if "$recursive"; then
do it in recursive style
fi
fi |
if "$tree_output"; then
output_as_tree
else
cat
fi
Example of "don't put programming application logic in the case branches" -- the touch command can take a -t timespec option or a -r referenceFile option but not both:
$ touch -t 202010100000 -r file1 file2
touch: cannot specify times from more than one source
Try 'touch --help' for more information.
I would implement that like (ignoring other options):
while getopts t:r: opt; do
case $opt in
t) timeSpec=$OPTARG ;;
r) refFile=$OPTARG ;;
esac
done
shift $((OPTIND-1))
if [[ -n $timeSpec && -n $refFile ]]; then
echo "touch: cannot specify times from more than one source" >&2
exit 1
fi
I would not do this:
while getopts t:r: opt; do
case $opt in
t) if [[ -n $refFile ]]; then
echo "touch: cannot specify times from more than one source" >&2
exit 1
fi
timeSpec=$OPTARG ;;
r) if [[ -n $timeSpec ]]; then
echo "touch: cannot specify times from more than one source" >&2
exit 1
fi
refFile=$OPTARG ;;
esac
done
You can see if the logic gets more complicated (as I mentioned, exactly one of -a or -b or -c), that the case statement size can easily balloon unmaintainably.

Bash script with named parameter containing equal sign

I am trying to pass some values to my bash script using named parameters similar to the following:
./script.sh --username='myusername' --password='superS3cret!' --domainou="OU=Groups with Space,OU=subou,DC=mydomain,DC=local"
I have the following code:
#!/bin/bash
while [ "$1" != "" ]; do
PARAM=`echo $1 | awk -vFPAT='([^=]*)|("[^"]+")' -vOFS="=" '{print $1}'`
VALUE=`echo $1 | awk -vFPAT='([^=]*)|("[^"]+")' -vOFS="=" '{print $2}'`
case $PARAM in
-u | --username)
username=$VALUE
;;
-p | --password)
password=$VALUE
;;
-ou | --domainou)
domainou=$VALUE
;;
*)
echo "ERROR: unknown parameter \"$PARAM\""
exit 1
;;
esac
shift
done
echo $username
echo "$password"
echo "$domainou"
What I get when I run my script is:
myusername
superS3cret!
OU
Now the first two lines are correct but obviously I don't want OU...
I want:
OU=Groups with Space,OU=subou,DC=mydomain,DC=local
Awk seems to be matching the = inside the quote. As best as I can tell the way to solve that is using
-vFPAT='([^=]*)|("[^"]+")' -vOFS="="
But clearly that's not working so I am just wondering if any awk gurus can help me understand what's wrong with my awk statement.
Thanks
Brad
You can do it like this:
#!/bin/bash
while [ $# -gt 0 ]; do
case "$1" in
-u=* | --username=*)
username="${1#*=}"
;;
-p=* | --password=*)
password="${1#*=}"
;;
-ou=* | --domainou=*)
domainou="${1#*=}"
;;
*)
printf "Error: unknown option: $1\n"
exit 1
esac
shift
done
printf "username: $username\n"
printf "password: $password\n"
printf "domainou: $domainou\n"
For parsing command line options that include both long and short optoins, consider using GNU getopt, which has support for long options. While it is possible to build-your-own parser replacement, using the getopt provides for more robust parsing:
Abbreviation of options (e.g., accepting --user for --username).
Checking for required/optional values
Error handling
See also: Using getopts to process long and short command line options
set $(getopt --long 'username:,password:,ou:,domain:' -o 'u:p:' -- "$0" "$#")
while [ "$#" -gt 0 ] ; do
OP=$1
shift
case "$OP" in
--) PROG=$1 ; shift ; break ;;
-u | --username) username=$1 ; shift ;;
-p | --password) password=$1 ; shift ;;
--ou | --domain) domainou=$1 ; shift ;;
esac
done
# Positional arguments are set ...
Below is what ultimately worked best for me.
#dash-o definitely got me pointed in the right direction but the script you provided was printing out extraneous info:
set: usage: set [-abefhkmnptuvxBCHP] [-o option-name] [--] [arg ...]
I believe the offending line was this:
set --long 'username:,password:,ou:,domain:' -o 'u:p:' -- "$0" "$#"
Here's the code that accomplished what I needed. I can't take credit for this. I stole it from here Using getopts to process long and short command line options but I never would have found that if not for dash-o so a big thank you!
#!/bin/bash
die() { echo "$*" >&2; exit 2; } # complain to STDERR and exit with error
needs_arg() { if [ -z "$OPTARG" ]; then die "No arg for --$OPT option"; fi; }
while getopts ab:c:-: OPT; do
# support long options: https://stackoverflow.com/a/28466267/519360
if [ "$OPT" = "-" ]; then # long option: reformulate OPT and OPTARG
OPT="${OPTARG%%=*}" # extract long option name
OPTARG="${OPTARG#$OPT}" # extract long option argument (may be empty)
OPTARG="${OPTARG#=}" # if long option argument, remove assigning `=`
fi
case "$OPT" in
u | username ) needs_arg; username="$OPTARG" ;;
p | password ) needs_arg; password="$OPTARG" ;;
o | domainou ) needs_arg; domainou="$OPTARG" ;;
??* ) die "Illegal option --$OPT" ;; # bad long option
\? ) exit 2 ;; # bad short option (error reported via getopts)
esac
done
shift $((OPTIND-1)) # remove parsed options and args from $# list
echo "$username"
echo "$password"
echo "$domainou"

How to get multiple argumen for getopt option?

I need help to write getopt function to handle multiple argument for one option like below example, appreciate your kind support.
Example:
./getopt.sh -s abcd -s efgh -s ijkl -s bdnc -e test
This is i got so far
#!/bin/bash
OPTS=`getopt -o s:e:h -n '$0' -- "$#"`
eval set -- "$OPTS"
while true; do
case "$1" in
-s ) SOURCE=$1 ;shift ;;
-h ) echo "$0 -s source -e enviroment"; shift ;;
-e ) ENV=$1; shift; shift ;;
* ) break ;;
esac
done
if [ $ENV='TEST' ];then
echo "execute on test with $SOURCE"
elif [ $ENV='DEV' ];then
echo "execute on dev with $SOURCE"
else
exit
fi
but here I want to execute -s multiple time.
You can use the same option multiple times and add all values to an array.
like:
while getopts "o:" i; do
case $i in
o) arr+=("$OPTARG");;
#...
esac
done

Why do I have error with multi long options using getopt in bash?

I just wrote a script in bash, which work expect for multi long option:
#!/bin/bash
OPTS=`getopt -q -o fdhl: -l free,df,help,log: -- "$*"`
#Check if error with getopt
if [ $? != 0 ]
then
echo -e "error: parameter could not be found\n\nUsage:\n supervision [options]\n\n Try 'supervision --help'\n or 'supervision -h'\n for additional help text." ;
exit 1
fi
eval set -- "$OPTS"
while true ; do
case "$1" in
-f|--free)
free -h ;
shift;;
-d|--df)
df -h ; #Run df system command
shift;;
-l|--log)
case "$2" in
"") echo "miss file" ;
shift 2;; #No file passed as parameter
*)
df -h >> "$2" ;
shift 2;;
esac ;;
-h|--help) #Display help
shift;;
--) #End of parsed parameters list
shift ; break ;;
*)
break ;;
esac
done
I don't get why i'm supposed to, when I use more than 1 long option, for example:
sh myscript --free --df
And when I use --log:
sh myscript --log logfile
Both case exit on the if [ $? != 0 ], seems like the element which follow the 1st long option doesn't get parsed.
Ok, I figured out and it's all due to the using of "$*" instead of "$#" in the getopt call. I don't exactly why, i guessed both do the same thing, but it turns out to be the one which causes the problem.

Resources