-
Notifications
You must be signed in to change notification settings - Fork 198
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Enable long, GNU style options RE: issue-1 #3
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -24,16 +24,27 @@ | |
### 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 | ||
LOG_LEVEL="${LOG_LEVEL:-6}" # 7 = debug -> 0 = emergency | ||
|
||
# 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 | ||
read -r -d '' usage <<-'EOF' | ||
-f [arg] Filename to process. Required. | ||
-t [arg] Location of tempfile. Default="/tmp/bar" | ||
-d Enables debug mode | ||
-h This page | ||
read -r -d '' usage <<-'EOF' || true # exits non-zero when EOF encountered | ||
-f --file [arg] Filename to process. Required. | ||
-t --temp [arg] Location of tempfile. Default="/tmp/bar" | ||
-v Enable verbose mode, print script as it is executed | ||
-d --debug Enables debug mode | ||
-h --help This page | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The second field is examined for leading There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. A very acceptable compromise imo too There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 👍 |
||
EOF | ||
|
||
# Set magic variables for current file and its directory. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I had to delete the whitespace at EOL error... because OCD There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes of course : ) I think I have turned it off in my editor for markdown trailing space preservation. I should really make a case-by-case exception going forward 😄 |
||
|
@@ -56,7 +67,7 @@ function _fmt () { | |
fi | ||
|
||
local color_reset="\x1b[0m" | ||
if [[ "${TERM}" != "xterm"* ]] || [ -t 1 ]; then | ||
if [[ "${TERM:-}" != "xterm"* ]] || [ -t 1 ]; then | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Allow some variables to be unset, without causing errors, mostly those provided by the system and built-ins that should be set in most reasonable environments, but if not, they won't cause problems. |
||
# Don't use colors on pipes or non-recognized terminals | ||
color=""; color_reset="" | ||
fi | ||
|
@@ -91,14 +102,26 @@ trap cleanup_before_exit EXIT | |
|
||
# Translate usage string -> getopts arguments, and set $arg_<flag> defaults | ||
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)}')" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Get the long option name from the second field, if it exists There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think it would be cool to use one method for long/short opt parsing. E.g. go There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sure I can do that |
||
# map long name back to short name | ||
varname="short_opt_${long_opt}" | ||
eval "${varname}=\"${opt}\"" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We'll use this later to make long options get processed as if the short option was used |
||
|
||
# check if option takes an argument | ||
varname="has_arg_${opt}" | ||
if ! echo "${line}" |egrep '\[.*\]' >/dev/null 2>&1; then | ||
init="0" # it's a flag. init with 0 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This line was unused, dead code so I removed it. |
||
eval "${varname}=0" | ||
else | ||
opt="${opt}:" # add : if opt has arg | ||
init="" # it has an arg. init with "" | ||
eval "${varname}=1" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We need some additional means of determining whether the long options have required arguments or not; the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It seems that even before these changes, if a required option argument was missing, subsequent options/text would get parsed as the required argument. It might be sensible to check that required arguments aren't just being populated with additional options/arguments. See also #5 |
||
fi | ||
opts="${opts}${opt}" | ||
opts="${opts:-}${opt}" | ||
|
||
varname="arg_${opt:0:1}" | ||
if ! echo "${line}" |egrep '\. Default=' >/dev/null 2>&1; then | ||
|
@@ -109,30 +132,44 @@ while read line; do | |
fi | ||
done <<< "${usage}" | ||
|
||
# Allow long options like --this | ||
opts="${opts}-:" | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is the trick. I saw it here: http://mywiki.wooledge.org/ComplexOptionParsing (second code block) |
||
# Reset in case getopts has been used previously in the shell. | ||
OPTIND=1 | ||
|
||
# Overwrite $arg_<flag> defaults with the actual CLI options | ||
while getopts "${opts}" opt; do | ||
line="$(echo "${usage}" |grep "\-${opt}")" | ||
[ "${opt}" = "?" ] && help "Invalid use of script: ${@} " | ||
|
||
if [ "${opt}" = "-" ]; then #OPTARG is long-option-name or long-option=value | ||
if [[ "${OPTARG}" =~ .*=.* ]]; then # --key=value format | ||
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}" | ||
|
||
[ "${opt}" = "?" ] && help "Invalid use of script: ${@} " | ||
varname="arg_${opt:0:1}" | ||
default="${!varname}" | ||
value="${OPTARG:-}" | ||
if [ -z "${OPTARG:-}" ] && [ "${default}" = "0" ]; then | ||
value="1" | ||
fi | ||
|
||
value="${OPTARG}" | ||
if [ -z "${OPTARG}" ] && [ "${default}" = "0" ]; then | ||
value="1" | ||
eval "${varname}=\"${value}\"" | ||
debug "cli arg ${varname} = ($default) -> ${!varname}" | ||
fi | ||
|
||
eval "${varname}=\"${value}\"" | ||
debug "cli arg ${varname} = ($default) -> ${!varname}" | ||
done | ||
|
||
shift $((OPTIND-1)) | ||
|
||
[ "$1" = "--" ] && shift | ||
[ "${1:-}" = "--" ] && shift | ||
|
||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Move to beginning for better error handling |
||
### Switches (like -d for debugmode, -h for showing helppage) | ||
|
@@ -144,6 +181,11 @@ if [ "${arg_d}" = "1" ]; then | |
LOG_LEVEL="7" | ||
fi | ||
|
||
# verbose mode | ||
if [ "${arg_v}" = "1" ]; then | ||
set -o verbose | ||
fi | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This hunk is just related to the additional |
||
# help mode | ||
if [ "${arg_h}" = "1" ]; then | ||
# Help exists with code 1 | ||
|
@@ -154,24 +196,14 @@ fi | |
### Validation (decide what's required for running your script and error out) | ||
##################################################################### | ||
|
||
[ -z "${arg_f}" ] && help "Setting a filename with -f is required" | ||
[ -z "${LOG_LEVEL}" ] && emergency "Cannot continue without LOG_LEVEL. " | ||
[ -z "${arg_f:-}" ] && help "Setting a filename with -f or --file is required" | ||
[ -z "${LOG_LEVEL:-}" ] && emergency "Cannot continue without LOG_LEVEL. " | ||
|
||
|
||
### Runtime | ||
##################################################################### | ||
|
||
# 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 | ||
|
||
if [[ "${OSTYPE}" == "darwin"* ]]; then | ||
if [[ "${OSTYPE:-}" == "darwin"* ]]; then | ||
info "You are on OSX" | ||
else | ||
info "You are on Linux" | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
More robust error checking from the beginning