Using space in case pattern in Bash - bash

I need find solution for using space in pattern in case. I have this function with case
setParam() {
case "$1" in
0624)
# do something - download file from url
;;
del-0624)
# do something - delete file from local machine
exit 0
;;
# Help
*|''|h|help)
printHelp
exit 0
;;
esac
}
for PARAM in $*; do
setParam "$PARAM"
done
Paramter "0624" is for run function for download files from url.
Paramter "del-0624" is for deleting files in local machine.
Question: It is possible use paramter "del 0624"(with space)? I have problem with space in parameter in case.

You need to use double quotes in case. Also script should be run as ./script "del 0624".
setParam() {
case "$1" in
"0624")
# do something - download file from url
;;
"del-0624")
# do something - delete file from local machine
exit 0
;;
"del 0624")
# do something - delete file from local machine
exit 0
;;
# Help
*|''|h|help)
printHelp
exit 0
;;
esac
}

You can set a custom IFS instead of the default space delimiter, in order to parse parameters that include spaces.
For example, this will replace the space before the - of all the script parameters with a TAB, and then grab the parameter name and value:
# Get script parameters, and split by tabs instead of space (for IFS)
SCRIPT_PARAMS="$*"
SCRIPT_PARAMS=${SCRIPT_PARAMS// -/$'\t'-}
IFS=$'\t'
for param in ${SCRIPT_PARAMS} ; do
# Get the next parameter (flag) name, without the value
param_name=${param%% *}
# Get the parameter value after the flag, without surrounding quotes
param_value=$(echo "${param#*"$param_name" }" | xargs)
case $param_name in
-b|--basic)
echo "Parse a basic parameter '${param_name}' without a value"
shift ;; # After a basic parameter shift just once
-c|--custom)
echo "Parse a custom parameter '${param_name}' with value of: ${param_value}"
shift 2 ;; # After a custom parameter shift twice
-*)
echo "Error - unrecognized parameter: ${param}" 1>&2
exit 1 ;;
*)
break ;;
esac
done
# Reset IFS
unset IFS

This is to amend #Arjun's answer:
If you want to match spaces and wildcards in the same branch, simply escape the space by prepending a \.
test.sh:
#!/usr/bin/env bash
case "$1" in
foo\ *)
echo "matched!" ;;
*)
echo "not matched!" ;;
esac
$ ./test.sh "foo bar"
matched!
$ ./test.sh "foobar"
not matched!

Related

Bash - Best way to have helpFunction & default parameter values

After a lot of read, SO or others, I'm really wondering about the best / cleanest way to have a bash script with parameters, optionals with default values.
Here is my script for now:
#!/bin/bash
helpFunction()
{
echo ""
echo "Usage: $0 --reload --mode=[single|cluster]"
echo -e "\t--reload Reload the database : fixtures & schema"
echo -e "\t--mode Mode of build : single or cluster"
exit 1
}
while [[ "$#" -gt 0 ]]; do
case $1 in
-h|--help) helpFunction; shift ;;
-r|--reload) reload=true; shift ;;
-m|--mode) mode="single"; shift ;;
# ... (same format for other required arguments)
*) echo "Unknown parameter passed: $1" ;;
esac
shift
done
./bin/sh/tools/build.sh -e local -m $mode -p local
For now, the $mode variable seems to not be set if I don't set it, how can I have a default value for this variable ? (default is single)
What I want is the user to call the script like (reload is true, mode is cluster):
bin/script.sh -r --mode=cluster
Or by default (reload is false, mode is single):
bin/script.sh
Is this the good way to wait for parameters ? I read other ways, but no real explanations.
Thanks.
There is no universal best/cleanest method; it eventually comes down to what works best for you; some ideas:
set mode to a default value before the while/case loop
after the while/case loop test mode and if unset/undefined then set to a
default value (should probably play it safe and unset mode before the while/case loop though in this case you might as well see idea #1)
pass to build.sh wrapped in double quotes (ie, build.sh -e local -m "$mode" -p local) and have build.sh test for the -m argument being unset/undefined and set to a default value
a variation on #2 and #3 is to use parameter substitution when
passing mode to build.sh, eg: build.sh -e local -m "${mode:-default_value}" -p local
if your script sources a config/ini file you could assign mode a
default value in said config/ini file and then make sure said config/ini file is sourced before the while/case loop
If you set the mode beforehand, that will be its default. If you want parameter with assignment, you need to grab $2 and shift twice.
mode="single"
while [[ "$#" -gt 0 ]]; do
case $1 in
-h|--help) helpFunction; shift ;;
-r|--reload) reload=true; shift ;;
-m|--mode) mode="$2"; shift; shift ;;
# ... (same format for other required arguments)
*) echo "Unknown parameter passed: $1" ;;
esac
shift
done
I think it's also good to restore the positional arguments. Since you may want to use them, but that depends on your script.
mode="single"
reload="false"
POSITIONAL=()
while [[ "$#" -gt 0 ]]; do
case $1 in
-h | --help)
helpFunction
shift
;;
-r | --reload)
reload=true
shift
;;
-m | --mode)
mode="$2"
shift
shift
;;
*)
POSITIONAL+=("$1")
shift
;;
esac
done
set -- "${POSITIONAL[#]}"
COMMAND="$1"
Then you could invoke a function that is called like the $COMMAND and pass the rest of the params with $#, and shift again and set the subcommand to $1. You can do this infinitely to get a chain of (sub)commands.
You can still check if a valid command was provided before you try to call the $COMMAND.
COMMAND_LIST="add issue revoke"
case $COMMAND_LIST in
*"$COMMAND"*)
"$COMMAND" "$#"
;;
*)
helpFunction
echo "ERROR: Unknown command: $COMMAND"
exit 1
;;
esac
But that is of course only if your script supports commands/subcommands. Otherwise, it makes sense to error right away like you did, if there is an unknown parameter.

Print out a list of all cases of a switch

Curious question. Is it somehow possible to print out all cases of a certain switch-case automatically in bash? In a way, such that it stays as maintainable as possible, meaning that one does not have to add any more code if a new case is added to print out that same case.
For instance, that would be useful if the cases represented commands. A help function could then print out all available commands.
There is no direct way to achieve this, but you can use an array to maintain your choices:
# Define your choices - be sure not to change their order later; only
# *append* new ones.
choices=( foo bar baz )
# Make the array elements the case branches *in order*.
case "$1" in
"${choices[0]}")
echo 'do foo'
;;
"${choices[1]}")
echo 'do bar'
;;
"${choices[2]}")
echo 'do baz'
;;
*)
echo "All choices: ${choices[#]}"
esac
This makes the branches less readable, but it's a manageable solution, if you maintain your array carefully.
Note how the branch conditions are enclosed in "..." so as to prevent the shell from interpreting the values as (glob-like) patterns.
That said, as chepner points out, perhaps you want to define your choices as patterns to match variations of a string:
In that event:
Define the pattern with quotes in the choices array; e.g., choices=( foo bar baz 'h*' )
Reference it unquoted in the case branch; e.g., ${choices[3]})
bash does not give you access to the tokens it parses and does not save case strings (which can be glob expressions as well).
Unfortunately, that means you will not be able to DRY your code in the way you were hoping.
After a while of hacking together several Bash 4 features I've got this one.
#!/bin/bash
set -euo pipefail
# create coprocess with 2 descriptors so we can read and write to them
coproc CAT { cat ; }
# creme de la creme of this solution - use function to both collect and select elements
function element {
echo "$1" >&${CAT[1]}
echo "$1"
}
case "$1" in
$(element A))
echo "Your choice is: A"
;;
$(element B))
echo "Your choice is: B"
;;
*)
echo "Your choice is not present in available options: $1"
# close writing descriptor
exec {CAT[1]}>&-
#read colected options into an array
mapfile -t OPTIONS <&${CAT[0]}
echo "Available options are: [ ${OPTIONS[#]} ]"
;;
esac
Output:
Your choice is not present in available options: C
Available options are: [ A B ]
There are 2 parts for this solution:
coproc - which creates subprocess for reading and writing from subshell
function element which both writes into descriptors of coproc subrocess and returns it's argument so we can use it inside case ... esac
If handling all options should be done outside of case then you can use ;;& feature of Bash 4 case statement which forces checking every statement inside case (usually - i.e. ;; - it stops after first match). This checking is needed so we can collect all options into an array later
There is probably a lot of reasons not to use this (limits of data which can be safely stored in descriptor without reading them being one of those) and I welcome all comments which can make this solution better.
You could have your script inspect itself:
#!/bin/bash
case_start=$(("$LINENO" + 2)) #store line number of start
case "$1" in
simple_case) echo "this is easy";;
--tricky)
echo "This is a tricky"
(echo "Multiline")
echo "Statement"
;;
-h|--help) echo "heeeelp me";;
-q|--quiet) ;;
*) echo "unknown option";;
esac
case_end=$(("$LINENO" - 2)) #store line number of end
# - take lines between $case_start and $case_end
# - replace newlines with spaces
# - replace ";;" with newlines
# -=> now every case statement should be on its own line
# - then filter out cases: delete everything after the first ")" including the ")" and trim blanks
cases_available=`sed -n "${case_start},${case_end}p" $0 | sed 's/#.*//' | tr '\n' ' ' | sed 's/;;/\n/g' | sed 's/).*//;s/[[:blank:]]*//'`
echo -e "cases_available:\n\n$cases_available"
this would print:
cases_available:
simple_case
--tricky
-h|--help
-q|--quiet
*
There are some pitfalls with this:
Comments or strings inside the case statement with a ";;" in it will break stuff
Can't handle nested switch case statements.
Dunno if I understood correctly your question, but you can do something like this page says to print out all the available cases as default choice:
case "$1" in
start)
start
;;
stop)
stop
;;
status)
status anacron
;;
restart)
stop
start
;;
condrestart)
if test "x`pidof anacron`" != x; then
stop
start
fi
;;
*)
echo $"Usage: $0 {start|stop|restart|condrestart|status}"
exit 1
esac
I think the best approach and also in OP regards is to actually print the switch case structure to a new file and then source it in the original file if needed.
echo "case "'"$1"'" in" > case.sh
echo " test)" >> case.sh
echo " echo "This case!"" >> case.sh
echo " ;;" >> case.sh
echo "esac" >> case.sh
chmod+x case.sh
./case.sh test
#This case!
This way you can easily use variables to build your switch / case condition.

How to create a flag with getopts to run a command

I need help with my getopts, i want to be able to run this command ( mount command) only if i pass a flag ( -d in this case).
below output is what i have on my script but it doesn't seem to work.
CHECKMOUNT=" "
while getopts ":d" opt
do
case "$opt" in
d) CHECKMOUNT="true" ;;
usage >&2
exit 1;;
esac
done
shift `expr $OPTIND-1`
FS_TO_CHECK="/dev"
if [ "$CHECKMOUNT" = "true" ]
then
if cat /proc/mounts | grep $FS_TO_CHECK > /dev/null; then
# Filesystem is mounted
else
# Filesystem is not mounted
fi
fi
Your script has a number of problems.
Here is the minimal list of fixes to get it working:
While is not a bash control statement, it's while. Case is important.
Whitespace is important: if ["$CHECKMOUNT"= "true"] doesn't work and should cause error messages. You need spaces around the brackets and around the =, like so: if [ "$CHECKMOUNT" = "true" ].
Your usage of getopts is incorrect, I'm guessing that you mistyped this copying an example: While getopts :d: opt should be: while getopts ":d" opt.
Your usage of shift is incorrect. This should cause error messages. Change this to: shift $((OPTIND-1)) if you need to shift OPTIND.
The bare text unknocn flag seems like a comment, precede it with #, otherwise you'll get an error when using an unknown option.
There is no usage function. Define one, or change usage in your \?) case to an echo with usage instructions.
Finally, if your script only requires a single optional argument, you might also simply process it yourself instead of using getopt - the first argument to your script is stored in the special variable $1:
if [ "$1" = "-d" ]; then
CHECKMOUNT="true"
elif [ "$1" != "" ]; then
usage >&2
exit 1
fi

getopts printing help when no cmd. line argument was matched

I'm trying to use getopts in bash to parse command line arguments, but I couldn't figure out how to implement "default" action, if no argument was matched (or no cmdline argument given).
This is silghtly simplified version of what I've tried so far:
while getopts v:t:r:s:c: name;
do
case $name in
v) VALIDATE=1;;
t) TEST=1;;
r) REPORT=1;;
s) SYNC=1;;
c) CLEAR=1;;
*) print_help; exit 2;;
\?) print_help; exit 2;;
esac
done
Is there any (simple) way to make it call print_help; exit 2; on non matching input?
Looking between your question and the comments on Aditya's answer, I'd recommend the following:
[getopts]$ cat go
#!/bin/bash
function print_help { echo "Usage" >&2 ; }
while getopts vtrsc name; do
case $name in
v) VALIDATE=1;;
t) TEST=1;;
r) REPORT=1;;
s) SYNC=1;;
c) CLEAR=1;;
?) print_help; exit 2;;
esac
done
echo "OPTIND: $OPTIND"
echo ${##}
shift $((OPTIND - 1))
while (( "$#" )); do
if [[ $1 == -* ]] ; then
echo "All opts up front, please." >&2 ; print_help ; exit 2
fi
echo $1
shift
done
Since each of those are boolean flag options, you don't need (and in fact, do not want) the arguments, so we get rid of the colons. None of those characters are in IFS, so we don't need to wrap that in quotes, it will be one token for getopts anyway.
Next, we change the \? to a single ? and get rid of the *, as the * would match before the literal \?, and we might as well combine the rules into a single default match. This is a good thing, since any option specified with a - prefix should be an option, and users will expect the program to fail if they specify an option you don't expect.
getopts will parse up to the first thing that isn't an argument, and set OPTIND to that position's value. In this case, we'll shift OPTIND - 1 (since opts are 0-indexed) off the front. We'll then loop through those args by shifting them off, echoing them or failing if they start with a -.
And to test:
[getopts]$ ./go
OPTIND: 1
0
[getopts]$ ./go -t -v go go
OPTIND: 3
4
go
go
[getopts]$ ./go -d -v go go
./go: illegal option -- d
Usage
[getopts]$ ./go -t go -v go -d
OPTIND: 2
5
go
All opts up front, please.
Usage
Try the following workaround:
# Parse the arguments.
while getopts ':h?f:' opts; do
case ${opts} in
f) # Foo argument.
;;
# My other arguments.
\? | h | *) # Prints help.
grep " .)\ #" $0
exit 0
;;
esac
done
So basically -?/-h would print the parameters with comments based on its own source. Specifying : before options will print help for any other unknown argument also.
v:t:r:s:c: should be in double quotes
"v:t:r:s:c:"
Based on the script you posted, maybe you don't require all those colons :
Also you don't need *)
You need to provide a leading colon in the getopts option string if you want to enable ? to match an invalid option -- :vtrsc. Also you don't need the backslash before the ?

How to get arguments with flags in Bash

I know that I can easily get positioned parameters like this in bash:
$0 or $1
I want to be able to use flag options like this to specify for what each parameter is used:
mysql -u user -h host
What is the best way to get -u param value and -h param value by flag instead of by position?
This example uses Bash's built-in getopts command and is from the Google Shell Style Guide:
a_flag=''
b_flag=''
files=''
verbose='false'
print_usage() {
printf "Usage: ..."
}
while getopts 'abf:v' flag; do
case "${flag}" in
a) a_flag='true' ;;
b) b_flag='true' ;;
f) files="${OPTARG}" ;;
v) verbose='true' ;;
*) print_usage
exit 1 ;;
esac
done
Note: If a character is followed by a colon (e.g. f:), that option is expected to have an argument.
Example usage: ./script -v -a -b -f filename
Using getopts has several advantages over the accepted answer:
the while condition is a lot more readable and shows what the accepted options are
cleaner code; no counting the number of parameters and shifting
you can join options (e.g. -a -b -c → -abc)
However, a big disadvantage is that it doesn't support long options, only single-character options.
This is the idiom I usually use:
while test $# -gt 0; do
case "$1" in
-h|--help)
echo "$package - attempt to capture frames"
echo " "
echo "$package [options] application [arguments]"
echo " "
echo "options:"
echo "-h, --help show brief help"
echo "-a, --action=ACTION specify an action to use"
echo "-o, --output-dir=DIR specify a directory to store output in"
exit 0
;;
-a)
shift
if test $# -gt 0; then
export PROCESS=$1
else
echo "no process specified"
exit 1
fi
shift
;;
--action*)
export PROCESS=`echo $1 | sed -e 's/^[^=]*=//g'`
shift
;;
-o)
shift
if test $# -gt 0; then
export OUTPUT=$1
else
echo "no output dir specified"
exit 1
fi
shift
;;
--output-dir*)
export OUTPUT=`echo $1 | sed -e 's/^[^=]*=//g'`
shift
;;
*)
break
;;
esac
done
Key points are:
$# is the number of arguments
while loop looks at all the arguments supplied, matching on their values inside a case statement
shift takes the first one away. You can shift multiple times inside of a case statement to take multiple values.
getopt is your friend.. a simple example:
function f () {
TEMP=`getopt --long -o "u:h:" "$#"`
eval set -- "$TEMP"
while true ; do
case "$1" in
-u )
user=$2
shift 2
;;
-h )
host=$2
shift 2
;;
*)
break
;;
esac
done;
echo "user = $user, host = $host"
}
f -u myself -h some_host
There should be various examples in your /usr/bin directory.
I propose a simple TLDR:; example for the un-initiated.
Create a bash script called greeter.sh
#!/bin/bash
while getopts "n:" arg; do
case $arg in
n) Name=$OPTARG;;
esac
done
echo "Hello $Name!"
You can then pass an optional parameter -n when executing the script.
Execute the script as such:
$ bash greeter.sh -n 'Bob'
Output
$ Hello Bob!
Notes
If you'd like to use multiple parameters:
extend while getops "n:" arg: do with more paramaters such as
while getops "n:o:p:" arg: do
extend the case switch with extra variable assignments. Such as o) Option=$OPTARG and p) Parameter=$OPTARG
To make the script executable:
chmod u+x greeter.sh
I think this would serve as a simpler example of what you want to achieve. There is no need to use external tools. Bash built in tools can do the job for you.
function DOSOMETHING {
while test $# -gt 0; do
case "$1" in
-first)
shift
first_argument=$1
shift
;;
-last)
shift
last_argument=$1
shift
;;
*)
echo "$1 is not a recognized flag!"
return 1;
;;
esac
done
echo "First argument : $first_argument";
echo "Last argument : $last_argument";
}
This will allow you to use flags so no matter which order you are passing the parameters you will get the proper behavior.
Example :
DOSOMETHING -last "Adios" -first "Hola"
Output :
First argument : Hola
Last argument : Adios
You can add this function to your profile or put it inside of a script.
Thanks!
Edit :
Save this as a a file and then execute it as yourfile.sh -last "Adios" -first "Hola"
#!/bin/bash
while test $# -gt 0; do
case "$1" in
-first)
shift
first_argument=$1
shift
;;
-last)
shift
last_argument=$1
shift
;;
*)
echo "$1 is not a recognized flag!"
return 1;
;;
esac
done
echo "First argument : $first_argument";
echo "Last argument : $last_argument";
Another alternative would be to use something like the below example which would allow you to use long --image or short -i tags and also allow compiled -i="example.jpg" or separate -i example.jpg methods of passing in arguments.
# declaring a couple of associative arrays
declare -A arguments=();
declare -A variables=();
# declaring an index integer
declare -i index=1;
# any variables you want to use here
# on the left left side is argument label or key (entered at the command line along with it's value)
# on the right side is the variable name the value of these arguments should be mapped to.
# (the examples above show how these are being passed into this script)
variables["-gu"]="git_user";
variables["--git-user"]="git_user";
variables["-gb"]="git_branch";
variables["--git-branch"]="git_branch";
variables["-dbr"]="db_fqdn";
variables["--db-redirect"]="db_fqdn";
variables["-e"]="environment";
variables["--environment"]="environment";
# $# here represents all arguments passed in
for i in "$#"
do
arguments[$index]=$i;
prev_index="$(expr $index - 1)";
# this if block does something akin to "where $i contains ="
# "%=*" here strips out everything from the = to the end of the argument leaving only the label
if [[ $i == *"="* ]]
then argument_label=${i%=*}
else argument_label=${arguments[$prev_index]}
fi
# this if block only evaluates to true if the argument label exists in the variables array
if [[ -n ${variables[$argument_label]} ]]
then
# dynamically creating variables names using declare
# "#$argument_label=" here strips out the label leaving only the value
if [[ $i == *"="* ]]
then declare ${variables[$argument_label]}=${i#$argument_label=}
else declare ${variables[$argument_label]}=${arguments[$index]}
fi
fi
index=index+1;
done;
# then you could simply use the variables like so:
echo "$git_user";
I like Robert McMahan's answer the best here as it seems the easiest to make into sharable include files for any of your scripts to use. But it seems to have a flaw with the line if [[ -n ${variables[$argument_label]} ]] throwing the message, "variables: bad array subscript". I don't have the rep to comment, and I doubt this is the proper 'fix,' but wrapping that if in if [[ -n $argument_label ]] ; then cleans it up.
Here's the code I ended up with, if you know a better way please add a comment to Robert's answer.
Include File "flags-declares.sh"
# declaring a couple of associative arrays
declare -A arguments=();
declare -A variables=();
# declaring an index integer
declare -i index=1;
Include File "flags-arguments.sh"
# $# here represents all arguments passed in
for i in "$#"
do
arguments[$index]=$i;
prev_index="$(expr $index - 1)";
# this if block does something akin to "where $i contains ="
# "%=*" here strips out everything from the = to the end of the argument leaving only the label
if [[ $i == *"="* ]]
then argument_label=${i%=*}
else argument_label=${arguments[$prev_index]}
fi
if [[ -n $argument_label ]] ; then
# this if block only evaluates to true if the argument label exists in the variables array
if [[ -n ${variables[$argument_label]} ]] ; then
# dynamically creating variables names using declare
# "#$argument_label=" here strips out the label leaving only the value
if [[ $i == *"="* ]]
then declare ${variables[$argument_label]}=${i#$argument_label=}
else declare ${variables[$argument_label]}=${arguments[$index]}
fi
fi
fi
index=index+1;
done;
Your "script.sh"
. bin/includes/flags-declares.sh
# any variables you want to use here
# on the left left side is argument label or key (entered at the command line along with it's value)
# on the right side is the variable name the value of these arguments should be mapped to.
# (the examples above show how these are being passed into this script)
variables["-gu"]="git_user";
variables["--git-user"]="git_user";
variables["-gb"]="git_branch";
variables["--git-branch"]="git_branch";
variables["-dbr"]="db_fqdn";
variables["--db-redirect"]="db_fqdn";
variables["-e"]="environment";
variables["--environment"]="environment";
. bin/includes/flags-arguments.sh
# then you could simply use the variables like so:
echo "$git_user";
echo "$git_branch";
echo "$db_fqdn";
echo "$environment";
#!/bin/bash
if getopts "n:" arg; then
echo "Welcome $OPTARG"
fi
Save it as sample.sh
and try running
sh sample.sh -n John
in your terminal.
If you're familiar with Python argparse, and don't mind calling python to parse bash arguments, there is a piece of code I found really helpful and super easy to use called argparse-bash
https://github.com/nhoffman/argparse-bash
Example take from their example.sh script:
#!/bin/bash
source $(dirname $0)/argparse.bash || exit 1
argparse "$#" <<EOF || exit 1
parser.add_argument('infile')
parser.add_argument('outfile')
parser.add_argument('-a', '--the-answer', default=42, type=int,
help='Pick a number [default %(default)s]')
parser.add_argument('-d', '--do-the-thing', action='store_true',
default=False, help='store a boolean [default %(default)s]')
parser.add_argument('-m', '--multiple', nargs='+',
help='multiple values allowed')
EOF
echo required infile: "$INFILE"
echo required outfile: "$OUTFILE"
echo the answer: "$THE_ANSWER"
echo -n do the thing?
if [[ $DO_THE_THING ]]; then
echo " yes, do it"
else
echo " no, do not do it"
fi
echo -n "arg with multiple values: "
for a in "${MULTIPLE[#]}"; do
echo -n "[$a] "
done
echo
I had trouble using getopts with multiple flags, so I wrote this code. It uses a modal variable to detect flags, and to use those flags to assign arguments to variables.
Note that, if a flag shouldn't have an argument, something other than setting CURRENTFLAG can be done.
for MYFIELD in "$#"; do
CHECKFIRST=`echo $MYFIELD | cut -c1`
if [ "$CHECKFIRST" == "-" ]; then
mode="flag"
else
mode="arg"
fi
if [ "$mode" == "flag" ]; then
case $MYFIELD in
-a)
CURRENTFLAG="VARIABLE_A"
;;
-b)
CURRENTFLAG="VARIABLE_B"
;;
-c)
CURRENTFLAG="VARIABLE_C"
;;
esac
elif [ "$mode" == "arg" ]; then
case $CURRENTFLAG in
VARIABLE_A)
VARIABLE_A="$MYFIELD"
;;
VARIABLE_B)
VARIABLE_B="$MYFIELD"
;;
VARIABLE_C)
VARIABLE_C="$MYFIELD"
;;
esac
fi
done
So here it is my solution. I wanted to be able to handle boolean flags without hyphen, with one hyphen, and with two hyphen as well as parameter/value assignment with one and two hyphens.
# Handle multiple types of arguments and prints some variables
#
# Boolean flags
# 1) No hyphen
# create Assigns `true` to the variable `CREATE`.
# Default is `CREATE_DEFAULT`.
# delete Assigns true to the variable `DELETE`.
# Default is `DELETE_DEFAULT`.
# 2) One hyphen
# a Assigns `true` to a. Default is `false`.
# b Assigns `true` to b. Default is `false`.
# 3) Two hyphens
# cats Assigns `true` to `cats`. By default is not set.
# dogs Assigns `true` to `cats`. By default is not set.
#
# Parameter - Value
# 1) One hyphen
# c Assign any value you want
# d Assign any value you want
#
# 2) Two hyphens
# ... Anything really, whatever two-hyphen argument is given that is not
# defined as flag, will be defined with the next argument after it.
#
# Example:
# ./parser_example.sh delete -a -c VA_1 --cats --dir /path/to/dir
parser() {
# Define arguments with one hyphen that are boolean flags
HYPHEN_FLAGS="a b"
# Define arguments with two hyphens that are boolean flags
DHYPHEN_FLAGS="cats dogs"
# Iterate over all the arguments
while [ $# -gt 0 ]; do
# Handle the arguments with no hyphen
if [[ $1 != "-"* ]]; then
echo "Argument with no hyphen!"
echo $1
# Assign true to argument $1
declare $1=true
# Shift arguments by one to the left
shift
# Handle the arguments with one hyphen
elif [[ $1 == "-"[A-Za-z0-9]* ]]; then
# Handle the flags
if [[ $HYPHEN_FLAGS == *"${1/-/}"* ]]; then
echo "Argument with one hyphen flag!"
echo $1
# Remove the hyphen from $1
local param="${1/-/}"
# Assign true to $param
declare $param=true
# Shift by one
shift
# Handle the parameter-value cases
else
echo "Argument with one hyphen value!"
echo $1 $2
# Remove the hyphen from $1
local param="${1/-/}"
# Assign argument $2 to $param
declare $param="$2"
# Shift by two
shift 2
fi
# Handle the arguments with two hyphens
elif [[ $1 == "--"[A-Za-z0-9]* ]]; then
# NOTE: For double hyphen I am using `declare -g $param`.
# This is the case because I am assuming that's going to be
# the final name of the variable
echo "Argument with two hypens!"
# Handle the flags
if [[ $DHYPHEN_FLAGS == *"${1/--/}"* ]]; then
echo $1 true
# Remove the hyphens from $1
local param="${1/--/}"
# Assign argument $2 to $param
declare -g $param=true
# Shift by two
shift
# Handle the parameter-value cases
else
echo $1 $2
# Remove the hyphens from $1
local param="${1/--/}"
# Assign argument $2 to $param
declare -g $param="$2"
# Shift by two
shift 2
fi
fi
done
# Default value for arguments with no hypheb
CREATE=${create:-'CREATE_DEFAULT'}
DELETE=${delete:-'DELETE_DEFAULT'}
# Default value for arguments with one hypen flag
VAR1=${a:-false}
VAR2=${b:-false}
# Default value for arguments with value
# NOTE1: This is just for illustration in one line. We can well create
# another function to handle this. Here I am handling the cases where
# we have a full named argument and a contraction of it.
# For example `--arg1` can be also set with `-c`.
# NOTE2: What we are doing here is to check if $arg is defined. If not,
# check if $c was defined. If not, assign the default value "VD_"
VAR3=$(if [[ $arg1 ]]; then echo $arg1; else echo ${c:-"VD_1"}; fi)
VAR4=$(if [[ $arg2 ]]; then echo $arg2; else echo ${d:-"VD_2"}; fi)
}
# Pass all the arguments given to the script to the parser function
parser "$#"
echo $CREATE $DELETE $VAR1 $VAR2 $VAR3 $VAR4 $cats $dir
Some references
The main procedure was found here.
More about passing all the arguments to a function here.
More info regarding default values here.
More info about declare do $ bash -c "help declare".
More info about shift do $ bash -c "help shift".

Resources