My option parsing snippet - bash

Here's a snippet that I use to parse options in Bash:
#!/bin/bash
PROGNAME=${0##*/}
PROGVERSION=0.1
wW='-4.5.5-double'
reName=
usage()
{
cat << EO
Script purpose goes here.
EO
cat <<EO | column -s\& -t
-h, --help & show this output
-r, --rename & renames confout to Your gro
-v, --version & show version information
-w, --workWith & gromax exec suffix
EO
}
SHORTOPTS="hvw:r"
LONGOPTS="help,version,workWith:rename"
ARGS=$(getopt -s bash --options $SHORTOPTS --longoptions $LONGOPTS --name $PROGNAME -- "$#")
eval set -- "$ARGS"
while true; do
case $1 in
-h|--help)
usage; exit 0;;
-v|--version)
echo "$PROGVERSION"; exit 0;;
-w|--workWith)
wW=$2; shift;;
-r|--rename)
reName="true"; shift;;
--)
shift; break;;
*)
shift; break;;
esac
shift
done
# ====================
## finally the script:
echo "rename:" $reName
echo ' wW:' $wW
This snippet parses option (-w) only if it is before the trigger (-r):
~/wrk/mlDn/vas/res/bbst: test.bash -w 'dfdff' -r
rename: true
wW: dfdff
~/wrk/mlDn/vas/res/bbst: test.bash -r -w 'dfdff'
rename: true
wW: -4.5.5-double
How can this be fixed? What is wrong with my snippet?

Remove the call to shift from the -r processing. It's removing the -w from ARGS.

The problem is that you are shifting twice: once when you see the -r option and then again when you exit the case statement. This causes you to jump over the -w option. Therefore, you should only shift once. Remove the shift from outside the case statement and do all the shifting inside it.
Change your code to:
while true; do
case $1 in
-h|--help)
usage; exit 0;;
-v|--version)
echo "$PROGVERSION"; exit 0;;
-w|--workWith)
wW=$2; shift 2;; # shift twice here
-r|--rename)
reName="true"; shift;;
--)
shift; break;;
*)
shift; break;;
esac
#shift # this shift is not required
done
echo "rename:" $reName
echo ' wW:' $wW

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.

Shell script with parameters?

I have a shell script that takes parameters, below is the code..
Right now it will only accept parameters if passed if called like this: script.sh --mode=load (or -m=load)
Is there a way to modify this so that it can be called with or without the "=" sign, so that I can call: script.sh --mode load (or -m load)
Ideally needs to work in pure bash as I don't have access to install additional tools, etc.
for i in "$#"
do
case $i in
-m=*|--mode=*)
MODE="${i#*=}"
if [[ $MODE =~ ^(dump|load)$ ]]; then
echo "" > /dev/null
else
bark "Invalid --mode set, set this to dump or load.";
exit 1
fi
;;
-p=*|--db-path=*)
DBPATH="${i#*=}"
;;
-d=*|--dump-dir=*)
DUMPDIR="${i#*=}"
;;
-l=*|--list-file=*)
TABLES="${i#*=}"
# check if file exists on disk
if [ -e $TABLES ]
then
echo "" >> /dev/null
else
bark "Table file not found!";
exit 1
fi
;;
-t=*|--tenant-name=*)
TENANT="${i#*=}"
# check if tenant is correct
if [[ $TENANT =~ ^($TENANT_LIST)$ ]]; then
echo "" >> /dev/null
else
bark "Tenant name does not match, aborting.";
exit 1
fi
;;
-s|--shared)
SHARED=YES
;;
*) usage # unknown option
;;
esac
done
My bash version:
bash --version
GNU bash, version 4.3.22(1)-release (powerpc-ibm-aix5.1.0.0)
Loop on $#. When $1 is "-m", do a shift. So in the next loop $1 will now be the argument to the -m option.
script.sh --mode load
# FIRST LOOP
$# is "--mode load"
$1 is "--mode"
shift
# SECOND LOOP
$# is "load"
$1 is "load"
This is also useful if you can specify many arguments instead of just one like you have right now. Error checking should be done to validate your argument values, and if a user did script.sh --mode with no other argument.
Don't reinvent the wheel.
If you're OK with just 1 character options, use the bash builtin getopts
#!/bin/bash
while getopts :m:p:d:l:t:s opt; do
case $opt in
m) mode=$OPTARG ;;
p) dbpath=$OPTARG ;;
d) dumpdir=$OPTARG ;;
l) tables=$OPTARG
# test file existence
;;
t) tenant=$OPTARG
# test tenant
;;
s) shared=YES ;;
:) echo "Missing argument for option -$OPTARG" >&2
exit 2
;;
*) echo "Invalid option -$OPTARG" >&2
exit 2
;;
esac
done
shift $((OPTIND - 1))
cat << SHOW_VARS
I have:
mode=$mode
dbpath=$dbpath
dumpdir=$dumpdir
tables=$tables
tenant=$tenant
shared=$shared
rest of args=$*
SHOW_VARS
Otherwise, you may be able to use the external getopt program to help parse your args. I don't have an AIX box to test on, so YMMV
tempargs=$(
getopt \
-o m:d:l:t:s \
--long mode:,db-path:,dump-dir:,list-file:,tenant-name:,shared \
-- "$#"
)
if [[ $? -ne 0 ]]; then echo "Error..." >&2; exit 2; fi
eval set -- "$tempargs"
while true; do
case $1 in
-m|--mode) mode=$2; shift 2;;
-p|--db-path) dbpath=$2; shift 2;;
-d|--dump-dir) dumpdir=$2; shift 2;;
-l|--list-file) tables=$2
# test file existence
shift 2
;;
-t|--tenant-name) tenant=$2
# test tenant
shift 2
;;
-s|--shared) shared=YES; shift;;
--) shift; break ;;
*) echo "Error..." >&2; exit 2 ;;
esac
done

How can I accept long arguments using getopts in Bash?

I'm trying to have my getops function run with multiple flags and arguments but instead of short (-f style) flag, I want to accept a long one (--flag style). For example:
if [ $# -lt 1 ]; then
usage >&2
exit 1
else
while $1 "hf:" opt; do
case $opt in
h)
echo "Here is the help menu:"
usage
;;
f)
ls -l $OPTARG >&2
;;
\?)
echo "Invalid option: -$OPTARG" >&2
;;
:)
echo "Option -$OPTARG requires an argument" >&2
exit 1
;;
esac
done
fi
I would like the -h and -f to be --help and --file respectively.
How do I do that?
getopt will do this for you. It handles short options, long options, options with and without arguments, -- to end option parsing, and more.
Boilerplate usage looks like:
options=$(getopt -o hf: -l help,file: -n "$0" -- "$#") || exit
eval set -- "$options"
while [[ $1 != -- ]]; do
case $1 in
-h|--help) echo "help!"; shift 1;;
-f|--file) echo "file! $2"; shift 2;;
*) echo "bad option: $1" >&2; exit 1;;
esac
done
shift
# Process non-option arguments.
for arg; do
echo "arg! $arg"
done

Search for one command line parameter before the rest

I'm reading my command line parameters using getopt, and I'm reading a configuration file using .:
test.sh:
#!/bin/bash
set -- `getopt C:a:b:c: "$#"`
C="default.cfg"
. $C
while [ $# -gt 0 ]; do
case "$1" in
-a) cfg1="$2"; shift;;
-b) cfg2="$2"; shift;;
-c) cfg3="$2"; shift;;
-C) C="$2"; #you'll see what this is for later
shift;;
--) shift;
break;;
-*) echo "invalid option";
exit 1;;
*) break;;
esac
shift
done
echo "cfg1 = $cfg1"
echo "cfg2 = $cfg2"
echo "cfg3 = $cfg3"
exit 0
default.cfg::
cfg1=hello
cfg2=there
cfg3=friend
This all works as expected:
$ ./test.sh
cfg1 = hello
cfg2 = there
cfg3 = friend
$ ./test.sh -b optional
cfg1 = hello
cfg2 = optional
cfg3 = friend
This issue is I want configurations to be prioritized in the following manner:
options given on the command line
options defined in the config file defined by the -C option
options defined in the default config file
So if I have this:
test.cfg:
cfg1=custom_file_1
cfg2=custom_file_2
I want to get this:
$ ./test.sh -b command_line -C test.cfg
cfg1 = custom_file_1
cfg2 = command_line
cfg3 = friend
I just can't figure out how to load the default config file, then search the options for -C, then load the custom config file, overwriting the default, then search the command line parameters AGAIN and overwrite the configs again. I'm pretty new to shell scripting, so forgive me if I'm missing something obvious.
You can preprocess the arguments and pull out the value you're looking for:
#!/bin/bash
args=$(getopt C:a:b:c: "$#")
eval set -- $args
conf="default.cfg"
source "$conf"
# pre-process the arguments and see if we can find -C
found=0
for opt in "$#"; do
if [[ $found -eq 1 ]] && [[ -f "$opt" ]]; then
source "$opt"
break
fi
if [[ "$opt" == "-C" ]]; then
found=1
fi
done
while [ $# -gt 0 ]; do
case "$1" in
-a) cfg1="$2"; shift;;
-b) cfg2="$2"; shift;;
-c) cfg3="$2"; shift;;
-C) shift;; #don't do anything with this
--) shift;
break;;
-*) echo "invalid option";
exit 1;;
*) break;;
esac
shift
done
echo "cfg1 = $cfg1"
echo "cfg2 = $cfg2"
echo "cfg3 = $cfg3"
exit 0
To overwrite variables, try to replace :
-C) C="$2";
with :
-C) . "$2";
And invoke it with :
./test.sh -C test.cfg -a command_line1 -b command_line2
Update :
For options in any order, you can try this :
C="default.cfg"
. $C
while getopts C:a:b:c: OPTION
do
case $OPTION in
a) cfg1_override=$OPTARG;;
b) cfg2_override=$OPTARG;;
c) cfg3_override=$OPTARG ;;
C) . $OPTARG;;
-) break;;
-*) echo "invalid option";
exit 1;;
*) break;;
esac
done
shift $(($OPTIND - 1))
cfg1="${cfg1_override-${cfg1}}"
cfg2="${cfg2_override-${cfg2}}"
cfg3="${cfg3_override-${cfg3}}"
echo "cfg1 = $cfg1"
echo "cfg2 = $cfg2"
echo "cfg3 = $cfg3"
exit 0
Based on Is it possible to specify the order getopts conditions are executed?
First source default.cfg.
Than scan your options for a -C option. Handle this one when found.
Finally use getopts and skip -C when you find it during getopts.

Bash: --help feature

Is it posseble to have a --help argument with getopts?
I'm currently using this to code the help feature:
#!/bin/bash
PROGNAME=${0##*/}
PROGVERSION=1.0
usage()
{
cat << EO
Prog description goes here.
Usage: $PROGNAME
Options:
EO
cat <<EO | column -s\& -t
-h|--help & show this output
-v|--version & show version information
EO
}
SHORTOPTS="hv"
LONGOPTS="help,version"
ARGS=$(getopt -s bash --options $SHORTOPTS \
--longoptions $LONGOPTS --name $PROGNAME -- "$#" )
eval set -- "$ARGS"
while true; do
case $1 in
-h|--help)
usage
exit 0
;;
-v|--version)
echo "$PROGVERSION"
exit 0
;;
--)
shift
break
;;
*)
shift
break
;;
esac
shift
done
The bash getopts builtin does not support long option names with the double-dash prefix. It only supports single-character options.

Resources