Bash multiple combinations in getopts - linux

I would like to make a script where you can give a couple of parameters with it:
while getopts ":a:b:c:" opt; do
case $opt in
a)
echo "-a was triggered
;;
b)
echo "-b was triggered
;;
\?)
echo "Invalid option: -$OPTARG" >&2
exit 1
;;
:)
echo "Option -$OPTARG requires an argument." >&2
exit 1
;;
esac
done
The code I have now works but the problem I have is that I want to give another function/echo if it combines.
Example:
When I do: .MyScript -ab than it should give another function that what is defined in "a" or in "b"
so a bit like:
ab) -> Script -a -b or Script -ab
echo "-ab was triggered"
What is the best solution to do this?
Any ideas, your free to post!

while getopts ":a:b:c:" opt; do
case $opt in
a)
echo "-a was triggered"
a_val=$OPTARG
;;
b)
echo "-b was triggered"
b_val=$OPTARG
;;
\?)
echo "Invalid option: -$OPTARG" >&2
exit 1
;;
:)
echo "Option -$OPTARG requires an argument." >&2
exit 1
;;
esac
done
if [[ -n $a_val ]]; then do something with "$a_val"; fi
if [[ -n $b_val ]]; then do something with "$b_val"; fi
The only situation this may cause confusion is if the user passes -a "" -- one workaround is:
a=false
b=false
while getopts ":a:b:c:" opt; do
case $opt in
a) echo "-a was triggered"; a_val=$OPTARG; a=true ;;
b) echo "-b was triggered"; b_val=$OPTARG; b=true ;;
esac
done
if $a; then do something with "$a_val"; fi
if $b; then do something with "$b_val"; fi
Here, there are no brackets: if $a; then ... because I'm invoking the command "true" or "false" and acting on that exit status.

UPDATED
You can use the getopt(1) linux utility. This is not a bash internal function, but this can handle long arguments.
Try this:
declare -A OPTARG
GetOpt() {
local prog="${BASH_SOURCE[0]}"
[ $# -eq 0 ] && echo "$prog: Bad call of GetOpts">&2 && exit 1
local longopt="$1"
shift
local tmp
tmp=$(getopt -n"$prog" -a -l "$longopt" -- '' "$#") || exit
eval "tmp=($tmp)"
local i
for((i=0;i<${#tmp[*]};++i)){
key=${tmp[i]#--}
[ -z "$key" ] && break
if [[ "${tmp[i+1]}" =~ ^-- ]]; then OPTARG[$key]=1
else OPTARG[$key]="${tmp[++i]}"
fi
}
for((j=0;++i<${#tmp[*]};++j)){ OPTFILE[j]=${tmp[i]};}
}
GetOpt "a b ab file:" "$#"
[ "${OPTARG[a]}" -a "${OPTARG[b]}" ] && OPTARG[ab]=1 && unset OPTARG[a] OPTARG[b]
[ "${OPTARG[a]}" ] && echo Do a
[ "${OPTARG[b]}" ] && echo Do b
[ "${OPTARG[ab]}" ] && echo Do ab
The defined GetOpt function will place the parsed command line argument with the checked long options to the associative array called OPTARG. If there is an error it will fail informing about the problem.
With getopt's -a option you can use -ab or --ab format. Keep in mind that if You have del defined, then the -d option (if -d is not specified) will expand to --del.
If You specify an arg with additional option it can contain spaces. E.g. if -l file: defined in getopt then it can be used as ./test --file="q w". ${OPTARG[file]} will be q w.
The OPTFILE array contains the optional arguments (args given after --).

Related

Bash: using getops for this specific use

I would need my bash scrip to work either with:
No arguments
./script.sh
Path argument (classic argument, will saved as var to var=$1)
./script.sh /root/home/dir/
With switch -a (with it's own argument)
./script.sh -a picture.jpeg
Or both combined
./script.sh -a picture.jpeg /root/home/dir/
I have something like this:
while getopts ":a:" opt; do
case $opt in
a)
I_ARGUMENT=$OPTARG
echo "A ARGUMENT IS: $OPTARG"
;;
:)
echo "-a requires argument"
;;
esac
done
And then something like this for the path argument:
if [ -z "$1" ]
then
:
else
PATH="$1"
fi
Which obviously doesn't work well (at all) together. Could you help me to combine this two things? Thank you.
How about something like this
pathToHomeSlashDir=~/dir/ #default value will be home/dir/
if [ $# -gt 0 ]
then
while [ $# -gt 0 ]
do
case "$1" in
-a)
if [ $# -gt 1 ]
then
image="$2"
shift
else
echo "Please insert -a argument"
exit
fi
;;
*)
pathToHomeSlashDir="$1"
;;
esac
shift
done
fi
You'd just need to handle the variables then

not allow multiple option in getopts of bash script

Having bash script as follows
#! /bin/bash
usage()
{
echo -e "need help!"
}
while getopts ":a:b:h" OPTION
do
case $OPTION in
a)
printf "a option with value %s\n" $OPTARG
;;
b)
printf "b option with value %s\n" $OPTARG
;;
h)
usage
;;
?)
echo -e "No option selected"
;;
esac
done
exit 0
Above script run fine with different option but i wanted to extend it to not allow to pass multiple option at same time like as following argument
$ ./test.bash -a 1 -b 2
a option with value 1
b option with value 2
should be not valid means some way it give me error like wrong syntax i achieved it by as follows but it seems to long it is as follow
#! /bin/bash
usage()
{
echo -e "need help!"
}
let "a_count=0"
let "b_count=0"
MY_ARG=""
while getopts ":a:b:h" OPTION
do
case $OPTION in
a)
let a_count=1
MY_ARG=$OPTARG
;;
b)
let b_count=1
MY_ARG=$OPTARG
;;
h)
usage
;;
?)
echo -e "No option selected"
;;
esac
done
[[ $a_count -eq 1 ]] && [[ $b_count -eq 1 ]] && echo "wrong command sytax" && exit 0
[[ $a_count -eq 1 ]] && printf "a option with value %s\n" $MY_ARG
[[ $b_count -eq 1 ]] && printf "b option with value %s\n" $MY_ARG
exit 0
run like
$ ./test.bash -a 1 -b 2
wrong command sytax
But i want to finish validation in while..loop of getopts. Also this validation not works for following command
./test.bash -a -b
a option with value -b
any one have batter idea how to use getopts for this type validation?
you've almost got it. The : after the a and after the b say that they take an argument, so your example with -a -b is actually valid, saying "There is option a with value -b".
If you really just want "-a or -b and then an argument", you probably don't need getopts at all, but should do:
[ "$1" == "-a" ] && printf "a option with value %s\n" $2
[ "$1" == "-b" ] && printf "b option with value %s\n" $2
any one have batter idea how to use getopts for this type validation?
well, actually, you're explicitly telling geptopts that -a and -b are not boolean parameters, but parameters that take an extra argument. The argument parser cannot tell whether the argument following -a is a parameter or its own argument, and thus they consider [-a ] [-b ] as syntax.
The best way, would actually be to have a different boolean parameter that matches the use case when you do not want an argument for -a and -b.
Though, it won't help you with your issue trying to have parameters with argument or boolean, but for the argument syntax checking you can try docopt which has a nicer way to create command line interface. You focus on doing the --help documentation, it parses it to build your parameter/argument parser. e.g.:
eval "$(docopts -V - -h - : "$#" <<EOF
Usage: myscript [(-a <foo> | -b <bar> | -abool | -bbool)]
-a <foo> The A option.
-b <bar> The B option.
-abool The A bool
-bbool The B bool
--help Show help options.
--version Print program version.
----
myscript 0.0.0
Copyright (C)20.. Your Name
License...
)"
if $a ; then
echo "a option! with $a"
fi
if $b ; then
echo "b option! with $b"
fi
if $abool ; then
echo "abool option!"
fi
if $bbool ; then
echo "bbool option!"
fi
This is not quite perfect because it will always process the first switch, but it does place an exit inside the while loop which is in keeping with your design requirement. It may give you an idea how to finish it.
#!/bin/bash
usage()
{
echo -e "need help!"
}
while getopts "a:b:h" OPTION
do
case $OPTION in
a)
aflag=1
aval=$OPTARG
if [ ! -z "$bflag" ]
then
printf "ERROR: cant use both -a and -b\n"
exit 1
fi
;;
b)
bflag=1
bval=$OPTARG
if [ ! -z "$aflag" ]
then
printf "ERROR: cant use both -a and -b\n"
exit 1
fi
;;
h) usage ;;
?) printf "ERROR" ; exit 1 ;;
esac
done
if [ ! -z "$aflag" ]
then
printf "a option with value %s $aval\n"
elif [ ! -z "$bflag" ]
then
printf "b option with value %s $bval\n"
fi
exit 0
If you still want to use the getopts I would use bash built-in variable for the arguments count $# to detect wrong number of arguments passed:
#! /bin/bash
usage()
{
echo -e "need help!"
}
# Check if number of arguments is greater than 2 as "-a1" (one arg) and "-a 2" are correct.
# You might want to check for other wrong inputs.
if [ $# > 2 ]
then
echo "Some warning t o the user or"
usage
exit 1
fi
while getopts ":a:b:h" OPTION
do
case $OPTION in
a)
printf "a option with value %s\n" $OPTARG
;;
b)
printf "b option with value %s\n" $OPTARG
;;
h)
usage
;;
?)
echo -e "No option selected"
;;
esac
done
exit 0

Multiple option arguments using getopts (bash)

I am trying to process command line arguments using getopts in bash. One of the requirements is for the processing of an arbitrary number of option arguments (without the use of quotes).
1st example (only grabs the 1st argument)
madcap:~/projects$ ./getoptz.sh -s a b c
-s was triggered
Argument: a
2nd example (I want it to behave like this but without needing to quote the argument"
madcap:~/projects$ ./getoptz.sh -s "a b c"
-s was triggered
Argument: a b c
Is there a way to do this?
Here's the code I have now:
#!/bin/bash
while getopts ":s:" opt; do
case $opt in
s) echo "-s was triggered" >&2
args="$OPTARG"
echo "Argument: $args"
;;
\?) echo "Invalid option: -$OPTARG" >&2
;;
:) echo "Option -$OPTARG requires an argument." >&2
exit 1
;;
esac
done
I think what you want is to get a list of values from a single option. For that, you can repeat the option as many times as needed, and add it's argument to an array.
#!/bin/bash
while getopts "m:" opt; do
case $opt in
m) multi+=("$OPTARG");;
#...
esac
done
shift $((OPTIND -1))
echo "The first value of the array 'multi' is '$multi'"
echo "The whole list of values is '${multi[#]}'"
echo "Or:"
for val in "${multi[#]}"; do
echo " - $val"
done
The output would be:
$ /tmp/t
The first value of the array 'multi' is ''
The whole list of values is ''
Or:
$ /tmp/t -m "one arg with spaces"
The first value of the array 'multi' is 'one arg with spaces'
The whole list of values is 'one arg with spaces'
Or:
- one arg with spaces
$ /tmp/t -m one -m "second argument" -m three
The first value of the array 'multi' is 'one'
The whole list of values is 'one second argument three'
Or:
- one
- second argument
- three
This's my way to do this with a user-defined-function: getopts-extra.
function getopts-extra () {
declare i=1
# if the next argument is not an option, then append it to array OPTARG
while [[ ${OPTIND} -le $# && ${!OPTIND:0:1} != '-' ]]; do
OPTARG[i]=${!OPTIND}
let i++ OPTIND++
done
}
# Use it within the context of `getopts`:
while getopts s: opt; do
case $opt in
s) getopts-extra "$#"
args=( "${OPTARG[#]}" )
esac
done
The full example of getoptz.sh:
#!/usr/bin/env bash
function getopts-extra () {
declare i=1
# if the next argument is not an option, then append it to array OPTARG
while [[ ${OPTIND} -le $# && ${!OPTIND:0:1} != '-' ]]; do
OPTARG[i]=${!OPTIND}
let i++ OPTIND++
done
}
function main () {
declare args
declare OPTIND OPTARG opt
while getopts :s: opt; do
case $opt in
s) getopts-extra "$#"
args=( "${OPTARG[#]}" )
;;
\?) echo "Invalid option: -$OPTARG" >&2
;;
:) echo "Option -$OPTARG requires an argument." >&2
exit 1
;;
esac
done
declare i
for i in "${!args[#]}"; do
echo "args[$i]: ${args[i]}"
done
}
main "$#"
exit
Test:
bash getoptz.sh -s a b c
Output:
args[0]: a
args[1]: b
args[2]: c
This function is a part of the bash lib called xsh-lib/core, available at the syntax xsh /util/getopts/extra.
You can parse the command-line arguments yourself, but the getopts command cannot be configured to recognize multiple arguments to a single option. fedorqui's recommendation is a good alternative.
Here is one way of parsing the option yourself:
while [[ "$*" ]]; do
if [[ $1 = "-s" ]]; then
# -s takes three arguments
args="$2 $3 $4"
echo "-s got $args"
shift 4
fi
done
Instead of using getops I found this better. Create a file tmp.sh and copy the following code..
usage() {
echo "Usage:
./tmp.sh --cloud_provider gcp --source_path abc --destination_path def --refresh_frequency 100 --authenticate true --credential_path a.json
"
}
while [ "$#" -gt 0 ]; do
case $1 in
-a | --authenticate)
shift
[[ $1 == -* ]] && continue
export authenticate=$1
;;
-cp | --credential_path)
shift
[[ $1 == -* ]] && continue
export credential_path=$1
;;
-c | --cloud_provider)
shift
[[ $1 == -* ]] && continue
export cloud_provider=$1
;;
-s | --source_path)
shift
[[ $1 == -* ]] && continue
export source_path=$1
;;
-d | --destination_path)
shift
[[ $1 == -* ]] && continue
export destination_path=$1
;;
-r | --refresh_frequency)
shift
[[ $1 == -* ]] && continue
export refresh_frequency=$1
;;
--)
shift
break
;;
esac
shift
done

Bash Shell Specify valid options for getops

This is the code I'm working with:
TYPE=""
FILE=""
while getopts "t:f:" opt; do
case $opt in
t) TYPE="$OPTARG"
;;
f) FILE="$OPTARG"
;;
esac
done
if [ -z "$TYPE" ]; then
echo "No -t. Bye."
exit 1 # error
else
if [ -n "$FILE" ]; then
echo "$TYPE and $FILE"
else
echo JUST $TYPE
fi
fi
Is it possible to specify valid options for $TYPE? For example valid type options are:
IMAGE, ZIP, DOC
If one of these types are specified as valid arguments then the script runs the existing line:
"echo "$TYPE and $FILE""
Otherwise it echos an error and quits. Is this possible to do?
If you need to filter out -t switch :
(...)
t)
case $OPTARG in
img|image|doc)
TYPE="$OPTARG"
;;
*)
echo >&2 "Unsupported type..."
exit 1
;;
esac
;;
(...)

Shell getopts for grabbing arguments

I have a script which should be run as either one of these two:
script.sh -t TYPE
script.sh -t TYPE -f FILE
If it is run without a -t flag I want it to error and exit.
If it is run with a -t flag I want to grab the value and store it in a
variable called "$TYPE" and print "JUST $TYPE"
If it is run with a -f flag I want it grab the value and store it in a variable called
"$FILE" and print "$TYPE and $FILE"
From information and tutorials on both here and the internet generally this is the closest I can get. Can anyone help me put in the second conditional into this existing code?
while getopts ":t:" opt; do
case $opt in
a)
echo "JUST $OPTARG" >&2
;;
\?)
echo "Error - Invalid type argument" >&2
exit 1
;;
:)
echo "Error - No type argument" >&2
exit 1
;;
esac
done
I think you get confused how you should handle command line arguments.
The common way is that the processing of all arguments precedes the actual job of the program/script.
Further more (related to getopts) if an option is appended by a colon, that indicates that the option is expected to have an argument.
Your case statement looks overpopulated too. You don't need to test for a colon and a question mark. The whole testing can be put after the while loop
I would do it like this
#!/bin/bash
unset TYPE
unset FILE
#uncomment if you want getopts to be silent
#OPTERR=0
while getopts "t:f:" opt; do
case $opt in
t)
TYPE=$OPTARG
echo "JUST $OPTARG"
;;
f)
FILE=$OPTARG
;;
esac
done
if ! test "$TYPE" ; then
echo "-t is obligatory"
exit 1
fi
if test "$TYPE" && test "$FILE" ; then
echo "$TYPE and $FILE"
fi
Have a look at this:
TYPE=""
FILE=""
while getopts "t:f:" opt; do
case $opt in
t) TYPE="$OPTARG"
;;
f) FILE="$OPTARG"
;;
esac
done
if [ -z "$TYPE" ]; then
echo "No -t. Bye."
exit 1 # error
else
if [ -n "$FILE" ]; then
echo "$TYPE and $FILE"
else
echo JUST $TYPE
fi
fi

Resources