-
-
Save jehiah/855086 to your computer and use it in GitHub Desktop.
#!/bin/sh | |
# | |
# a simple way to parse shell script arguments | |
# | |
# please edit and use to your hearts content | |
# | |
ENVIRONMENT="dev" | |
DB_PATH="/data/db" | |
function usage() | |
{ | |
echo "if this was a real script you would see something useful here" | |
echo "" | |
echo "./simple_args_parsing.sh" | |
echo "\t-h --help" | |
echo "\t--environment=$ENVIRONMENT" | |
echo "\t--db-path=$DB_PATH" | |
echo "" | |
} | |
while [ "$1" != "" ]; do | |
PARAM=`echo $1 | awk -F= '{print $1}'` | |
VALUE=`echo $1 | awk -F= '{print $2}'` | |
case $PARAM in | |
-h | --help) | |
usage | |
exit | |
;; | |
--environment) | |
ENVIRONMENT=$VALUE | |
;; | |
--db-path) | |
DB_PATH=$VALUE | |
;; | |
*) | |
echo "ERROR: unknown parameter \"$PARAM\"" | |
usage | |
exit 1 | |
;; | |
esac | |
shift | |
done | |
echo "ENVIRONMENT is $ENVIRONMENT"; | |
echo "DB_PATH is $DB_PATH"; |
I think that it is better to replace https://gist.github.com/jehiah/855086#file-simple_args_parsing-sh-L26
VALUE=`echo $1 | sed 's/^[^=]*=//g'`
sample:
/bin/sh myscript --host=localhost --uri="/some/uri/?debug=1"
@hypersoft: I really don't see how your code is any simpler... it is much less readable; and significantly less portable. This gist would appear to be intended as a shell script; not a bash script; and while the original does have one bashism, it seems like a trivial oversight (should be just usage()
rather than function usage()
. Your code includes using GNU getopt; herestrings, and many, many bash-only features that make it completely unsuited for any type of portable shell script.
Thanks, works even on Git Bash on Windows.
Thank you for this. But why you didn't use$0
in order to retrieve the script name ?
Shouldn't line https://gist.github.com/jehiah/855086#file-simple_args_parsing-sh-L44 be shift 2
?
I am trying it and the parameters are shifted one by one, when (I think) the loop expects them to move two by two.
Anyway, thanks for this, saves a bunch of time 👍
Better than writing the parsing code is having one generated and that's the reason why I have started the argbash project here on Github. Keeping the usage
function up-to-date and having support for positional arguments are some of the obvious benefits.
There is even an online bash 3.0+ compatible parsing code generator at https://argbash.io/generate
I've started the run-sections project if you need more than just parsing arguments. With run-sections you can chose what part of you code do you want to run and much more. You can simply mark sections inside your script and run just that sections. If you are interested on this idea, please have a look on https://github.com/stancufm/run-sections . It's very easy to work with this function.
./simple_args_parsing.sh
./simple_args_parsing.sh: 13: ./simple_args_parsing.sh: Syntax error: "(" unexpected
Soo... not great.
Hotfix this
function usage()
{
With :
usage() {
Nice gist conception!
Just a small detail, add -e to echo commands in the help menu, so it will format special tab characters ...
echo "\t-h --help"
shouldn't it be echo -e? Otherwise it prints \t in my case.
Or better use printf?
@geoff-codes thanks for the snippet! @hypersoft's solution was hella less readable
Thank you a lot
@gperreymond is right, without function keyword is better than with it.
@hypersoft 👎 You've complicated a really simple task.
Thanks, works fine. Use echo -e "\t-h --help"
as @bestjejust suggested for printing out tab.
I have difficulties for parsing "-e" with that, I think it interferes with the 'echo $1
line 25.
This works fine with or without an equal symbol.
while [ "$1" != "" ]; do
PARAM=`printf "%s\n" $1 | awk -F= '{print $1}'`
VALUE=`printf "%s\n" $1 | sed 's/^[^=]*=//g'`
if [[ $VALUE == $PARAM ]]; then
shift
VALUE=$1
fi
...
done
@perepechaev An even better way would be
awk -F= '{OFS="=";$1=""; printf substr($0,2)}'
In case that $VALUE is not provided returns an empty string
This works fine with or without an equal symbol.
while [ "$1" != "" ]; do PARAM=`printf "%s\n" $1 | awk -F= '{print $1}'` VALUE=`printf "%s\n" $1 | sed 's/^[^=]*=//g'` if [[ $VALUE == $PARAM ]]; then shift VALUE=$1 fi ... done
If you change if [[ $VALUE == $PARAM ]]
" to if [ "$VALUE" = "$PARAM" ]
it will be more portable.
The code in the OP was written to be run as /bin/sh, so the [[ ]] and == parts could be a problem since they're not POSIX.
Also, I think it's better to change PARAM=printf "%s\n" $1 | awk -F= '{print $1}'
to PARAM="$(printf "%s\n" $1 | awk -F= '{print $1}')"
and VALUE=printf "%s\n" $1 | sed 's/^[^=]*=//g'
to VALUE="$(printf "%s\n" $1 | sed 's/^[^=]*=//g')"
I wrote this - https://github.com/unfor19/bargs - exactly for this need, so you don't need to write down while case esac shift
thingy, a very simple and flexible implementation
I wrote another optional parser and generator that POSIX-compliant and works with all POSIX shells (e.g. dash, bash 2.0+, ksh88, zsh 3.1+, busybox ash). It is a pure shell function and aims to be compatible with POSIX and GNU option syntax. Its code is as fast and small as possible, and the CC0 license allows you to embed it freely in your scripts. https://github.com/ko1nksm/getoptions
2020-11-19: Add support for abbreviated long options and subcommands.
I used the work of @hypersoft for my own script and made some bug fixes and improvements to meet my needs. Giving back the results, which I have simplified for the sake of clarity.
#!/bin/bash
DIRNAME=$(dirname -- $0)
BASENAME=$(basename -- $0)
usage()
{
echo "USAGE: $BASENAME [OPTIONS]"
echo
echo "OPTIONS"
echo " -r, --require-approval Ask user for security approval when deploying CDK stacks"
echo
echo " Features deployed will be the sum \(union\) of the following options, if none"
echo " is specified then all features will be deployed by default. \(Specify each"
echo " option separately, as a group like -yld will not be understood\)"
echo " -a, --all Deploy all features"
echo " -y, --analytics Deploy the analytics features"
echo
echo " -v, --verbose [level] Output additional info. (can use -v=2 form also)"
echo " Possible levels:"
echo " 0 \(default\) essential info"
echo " 1 more info"
echo " 2 even more info"
echo
echo "POSITIONAL PARAMETERS"
echo " none"
exit
}
# default values for flags. always use 1 for true and 0 for false. use strings for qualifiers.
REQUIREAPPROVAL="--require-approval never"
DEFAULT=1
ALL=0
ANALYTICS=0
VERBOSELEVEL="0"
while [[ $# > 0 ]]
do
echo num $#
[[ $1 =~ ^-h$|^--help$ ]] && {
usage
};
# -- marks end of options and start of positional parameters
[[ $1 == -- ]] && { shift; break; };
# options without parameters
[[ $1 =~ ^-r$|^--require-approval$ ]] && { REQUIREAPPROVAL=; shift 1; continue; };
[[ $1 =~ ^-a$|^--all$ ]] && { ALL=1; DEFAULT=0; shift 1; continue; };
[[ $1 =~ ^-y$|^--analytics$ ]] && { ANALYTICS=1; DEFAULT=0; shift 1; continue; };
# verbose option with optional value. e.g. -v, -v 0, -v 1, --verbose, --verbose 1
[[ $1 =~ ^-v$|^--verbose$ ]] && {
shift 1
[[ $# == 0 ]] && { continue; };
VERBOSELEVEL="$1"
};
# verbose option with = value. e.g. -v=0, -v=1, -v=2, --verbose=1 etc.
[[ $1 =~ ^-v=|^--verbose= ]] && {
VERBOSELEVEL="${1#*=}"
shift 1
continue;
};
break;
done
# validate string parameters
[[ ! $VERBOSELEVEL =~ ^[0-2]$ ]] && {
echo illegal --verbose parameter $VERBOSELEVEL
usage
};
# if verbose 1 or 2
[[ ! $VERBOSELEVEL =~ ^[1-2]$ ]] && {
echo DEFAULT == $DEFAULT
echo ALL == $ALL
echo ANALYTICS == $ANALYTICS
};
# the default is to deploy all stacks
ALL=$(( $ALL || $DEFAULT ))
# Logic for which stacks are deployed based on the features needed
CFNMAIN=$(( $ALL ))
CFNANALYTICS=$(( $ALL || $ANALYTICS ))
# if verbose 2 only
[[ ! $VERBOSELEVEL =~ ^2$ ]] && {
echo CFNMAIN = $CFNMAIN
echo CFNANALYTICS = $CFNANALYTICS
};
[[ $CFNMAIN ]] && {
echo cdk synth cfnMain
echo cdk deploy $REQUIREAPPROVAL cfnMain
};
[[ $CFNANALYTICS ]] && {
echo cdk synth cfnAnalytics
echo cdk deploy $REQUIREAPPROVAL cfnAnalytics
};
echo ":) Deployment complete"
#!/bin/sh
DIRNAME=$(dirname -- $0)
BASENAME=$(basename -- $0)usage()
{
echo "USAGE: $BASENAME [OPTIONS]"
echo
echo "OPTIONS"
echo " -r, --require-approval Ask user for security approval when deploying CDK stacks"
echo
echo " Features deployed will be the sum (union) of the following options, if none"
echo " is specified then all features will be deployed by default. (Specify each"
echo " option separately, as a group like -yld will not be understood)"
echo " -a, --all Deploy all features"
echo " -y, --analytics Deploy the analytics features"
echo
echo " -v, --verbose [level] Output additional info. (can use -v=2 form also)"
echo " Possible levels:"
echo " 0 (default) essential info"
echo " 1 more info"
echo " 2 even more info"
echo
echo "POSITIONAL PARAMETERS"
echo " none"
exit
}default values for flags. always use 1 for true and 0 for false. use strings for qualifiers.
REQUIREAPPROVAL="--require-approval never"
DEFAULT=1
ALL=0
ANALYTICS=0
VERBOSELEVEL="0"while [[ $# > 0 ]]
do
echo num $#
[[$1 =~ ^-h$ |^--help$ ]] && {
usage
};
# -- marks end of options and start of positional parameters
[[ $1 == -- ]] && { shift; break; };# options without parameters [[ $1 =~ ^-r$|^--require-approval$ ]] && { REQUIREAPPROVAL=; shift 1; continue; }; [[ $1 =~ ^-a$|^--all$ ]] && { ALL=1; DEFAULT=0; shift 1; continue; }; [[ $1 =~ ^-y$|^--analytics$ ]] && { ANALYTICS=1; DEFAULT=0; shift 1; continue; }; # verbose option with optional value. e.g. -v, -v 0, -v 1, --verbose, --verbose 1 [[ $1 =~ ^-v$|^--verbose$ ]] && { shift 1 [[ $# == 0 ]] && { continue; }; VERBOSELEVEL="$1" }; # verbose option with = value. e.g. -v=0, -v=1, -v=2, --verbose=1 etc. [[ $1 =~ ^-v=|^--verbose= ]] && { VERBOSELEVEL="${1#*=}" shift 1 continue; }; break;
done
validate string parameters
[[ !
$VERBOSELEVEL =~ ^[0-2]$ ]] && {
echo illegal --verbose parameter $VERBOSELEVEL
usage
};if verbose 1 or 2
[[ !
$VERBOSELEVEL =~ ^[1-2]$ ]] && {
echo DEFAULT == $DEFAULT
echo ALL == $ALL
echo ANALYTICS == $ANALYTICS
};the default is to deploy all stacks
ALL=$(( $ALL || $DEFAULT ))
Logic for which stacks are deployed based on the features needed
CFNMAIN=$(( $ALL ))
CFNANALYTICS=$(( $ALL || $ANALYTICS ))if verbose 2 only
[[ !
$VERBOSELEVEL =~ ^2$ ]] && {
echo CFNMAIN = $CFNMAIN
echo CFNANALYTICS = $CFNANALYTICS
};[[ $CFNMAIN ]] && {
echo cdk synth cfnMain
echo cdk deploy $REQUIREAPPROVAL cfnMain
};[[ $CFNANALYTICS ]] && {
echo cdk synth cfnAnalytics
echo cdk deploy $REQUIREAPPROVAL cfnAnalytics
};echo ":) Deployment complete"
It's marked as /bin/sh, but will not work in posix compliant shells (such as dash).
The "[[ ]]" for comparisons, "~=" regex matching, and "==" instead of "=" are all not supported.
If you prefer using those to the posix equivalents, the easiest thing to do would be to just change it to /bin/bash since those are bashisms.
:)
/bin/bash it is, then!
👎 You've complicated a really simple task..