Merge pull request #3 from zbeekman/longargs

Enable long, GNU style options RE: issue-1
This commit is contained in:
Kevin van Zonneveld 2016-02-16 20:50:54 +01:00
commit e90ad7421f
2 changed files with 70 additions and 37 deletions

98
main.sh
View File

@ -24,16 +24,27 @@
### Configuration ### Configuration
##################################################################### #####################################################################
# Exit on error. Append ||true if you expect an error.
# `set` is safer than relying on a shebang like `#!/bin/bash -e` because that is neutralized
# when someone runs your script as `bash yourscript.sh`
set -o errexit
set -o nounset
# Bash will remember & return the highest exitcode in a chain of pipes.
# This way you can catch the error in case mysqldump fails in `mysqldump |gzip`
set -o pipefail
# Environment variables and their defaults # Environment variables and their defaults
LOG_LEVEL="${LOG_LEVEL:-6}" # 7 = debug -> 0 = emergency LOG_LEVEL="${LOG_LEVEL:-6}" # 7 = debug -> 0 = emergency
# Commandline options. This defines the usage page, and is used to parse cli # Commandline options. This defines the usage page, and is used to parse cli
# opts & defaults from. The parsing is unforgiving so be precise in your syntax # opts & defaults from. The parsing is unforgiving so be precise in your syntax
read -r -d '' usage <<-'EOF' read -r -d '' usage <<-'EOF' || true # exits non-zero when EOF encountered
-f [arg] Filename to process. Required. -f --file [arg] Filename to process. Required.
-t [arg] Location of tempfile. Default="/tmp/bar" -t --temp [arg] Location of tempfile. Default="/tmp/bar"
-d Enables debug mode -v Enable verbose mode, print script as it is executed
-h This page -d --debug Enables debug mode
-h --help This page
EOF EOF
# Set magic variables for current file and its directory. # Set magic variables for current file and its directory.
@ -56,7 +67,7 @@ function _fmt () {
fi fi
local color_reset="\x1b[0m" local color_reset="\x1b[0m"
if [[ "${TERM}" != "xterm"* ]] || [ -t 1 ]; then if [[ "${TERM:-}" != "xterm"* ]] || [ -t 1 ]; then
# Don't use colors on pipes or non-recognized terminals # Don't use colors on pipes or non-recognized terminals
color=""; color_reset="" color=""; color_reset=""
fi fi
@ -91,14 +102,26 @@ trap cleanup_before_exit EXIT
# Translate usage string -> getopts arguments, and set $arg_<flag> defaults # Translate usage string -> getopts arguments, and set $arg_<flag> defaults
while read line; do while read line; do
opt="$(echo "${line}" |awk '{print $1}' |sed -e 's#^-##')" # fetch single character version of option sting
opt="$(echo "${line}" |awk 'match($1,"^-.",a){print substr(a[0],2,1)}')"
# fetch long version if present
long_opt="$(echo "${line}" |awk 'match($2,"^--.*",a){print substr(a[0],3)}')"
# map long name back to short name
varname="short_opt_${long_opt}"
eval "${varname}=\"${opt}\""
# check if option takes an argument
varname="has_arg_${opt}"
if ! echo "${line}" |egrep '\[.*\]' >/dev/null 2>&1; then if ! echo "${line}" |egrep '\[.*\]' >/dev/null 2>&1; then
init="0" # it's a flag. init with 0 init="0" # it's a flag. init with 0
eval "${varname}=0"
else else
opt="${opt}:" # add : if opt has arg opt="${opt}:" # add : if opt has arg
init="" # it has an arg. init with "" init="" # it has an arg. init with ""
eval "${varname}=1"
fi fi
opts="${opts}${opt}" opts="${opts:-}${opt}"
varname="arg_${opt:0:1}" varname="arg_${opt:0:1}"
if ! echo "${line}" |egrep '\. Default=' >/dev/null 2>&1; then if ! echo "${line}" |egrep '\. Default=' >/dev/null 2>&1; then
@ -109,30 +132,44 @@ while read line; do
fi fi
done <<< "${usage}" done <<< "${usage}"
# Allow long options like --this
opts="${opts}-:"
# Reset in case getopts has been used previously in the shell. # Reset in case getopts has been used previously in the shell.
OPTIND=1 OPTIND=1
# Overwrite $arg_<flag> defaults with the actual CLI options # Overwrite $arg_<flag> defaults with the actual CLI options
while getopts "${opts}" opt; do while getopts "${opts}" opt; do
line="$(echo "${usage}" |grep "\-${opt}")"
[ "${opt}" = "?" ] && help "Invalid use of script: ${@} " [ "${opt}" = "?" ] && help "Invalid use of script: ${@} "
varname="arg_${opt:0:1}"
default="${!varname}"
value="${OPTARG}" if [ "${opt}" = "-" ]; then #OPTARG is long-option-name or long-option=value
if [ -z "${OPTARG}" ] && [ "${default}" = "0" ]; then if [[ "${OPTARG}" =~ .*=.* ]]; then # --key=value format
value="1" long=${OPTARG/=*/}
eval "opt=\"\${short_opt_${long}}\"" # Set opt to the short option corresponding to the long option
OPTARG=${OPTARG#*=}
else # --key value format
eval "opt=\"\${short_opt_${OPTARG}}\"" # Map long name to short version of option
eval "OPTARG=\"\${@:OPTIND:\${has_arg_${opt}}}\"" # Only assign OPTARG if option takes an argument
((OPTIND+=has_arg_${opt})) # shift over the argument if argument is expected
fi
# we have set opt/OPTARG to the short value and the argument as OPTARG if it exists
else
varname="arg_${opt:0:1}"
default="${!varname}"
value="${OPTARG:-}"
if [ -z "${OPTARG:-}" ] && [ "${default}" = "0" ]; then
value="1"
fi
eval "${varname}=\"${value}\""
debug "cli arg ${varname} = ($default) -> ${!varname}"
fi fi
eval "${varname}=\"${value}\""
debug "cli arg ${varname} = ($default) -> ${!varname}"
done done
shift $((OPTIND-1)) shift $((OPTIND-1))
[ "$1" = "--" ] && shift [ "${1:-}" = "--" ] && shift
### Switches (like -d for debugmode, -h for showing helppage) ### Switches (like -d for debugmode, -h for showing helppage)
@ -144,6 +181,11 @@ if [ "${arg_d}" = "1" ]; then
LOG_LEVEL="7" LOG_LEVEL="7"
fi fi
# verbose mode
if [ "${arg_v}" = "1" ]; then
set -o verbose
fi
# help mode # help mode
if [ "${arg_h}" = "1" ]; then if [ "${arg_h}" = "1" ]; then
# Help exists with code 1 # Help exists with code 1
@ -154,24 +196,14 @@ fi
### Validation (decide what's required for running your script and error out) ### Validation (decide what's required for running your script and error out)
##################################################################### #####################################################################
[ -z "${arg_f}" ] && help "Setting a filename with -f is required" [ -z "${arg_f:-}" ] && help "Setting a filename with -f or --file is required"
[ -z "${LOG_LEVEL}" ] && emergency "Cannot continue without LOG_LEVEL. " [ -z "${LOG_LEVEL:-}" ] && emergency "Cannot continue without LOG_LEVEL. "
### Runtime ### Runtime
##################################################################### #####################################################################
# Exit on error. Append ||true if you expect an error. if [[ "${OSTYPE:-}" == "darwin"* ]]; then
# `set` is safer than relying on a shebang like `#!/bin/bash -e` because that is neutralized
# when someone runs your script as `bash yourscript.sh`
set -o errexit
set -o nounset
# Bash will remember & return the highest exitcode in a chain of pipes.
# This way you can catch the error in case mysqldump fails in `mysqldump |gzip`
set -o pipefail
if [[ "${OSTYPE}" == "darwin"* ]]; then
info "You are on OSX" info "You are on OSX"
else else
info "You are on Linux" info "You are on Linux"

View File

@ -2,9 +2,10 @@ B3BP:STDIO_REPLACE_DATETIMES
Help using {root}/main.sh Help using {root}/main.sh
-f [arg] Filename to process. Required. -f --file [arg] Filename to process. Required.
-t [arg] Location of tempfile. Default="{tmpdir}/bar" -t --temp [arg] Location of tempfile. Default="{tmpdir}/bar"
-d Enables debug mode -v Enable verbose mode, print script as it is executed
-h This page -d --debug Enables debug mode
-h --help This page
{datetime} UTC [ info] Cleaning up. Done {datetime} UTC [ info] Cleaning up. Done