From 7cf9ea708d8ca09b2d0279fed30f58ea8bd69816 Mon Sep 17 00:00:00 2001 From: Izaak Beekman Date: Fri, 10 Feb 2017 09:03:07 -0500 Subject: [PATCH] Issue 44 issue 21 issue 47 (#85) * Fix shifting over `--`: don't throw errexit Fixes #21 * Add error trapping (including Ctrl-C) info to FAQ Closes #47 * Add backtracing to help localize errors - Fixes #44 - Backtracing is turned on when the debugging `-d` flag is passed, otherwise backtracing function defined but signal trap not set. - Update main-help fixture due to moving trap * Untabify main.sh Might be a controversial move, but I loath tabs... * Add checks for tab chars and trailing whitespace - Update poor-man's style enforcement script to check for these violations - Better document style guidlines in README with small tweaks * Add a magic variable to indicate if being source if `__i_am_main_script=1 #true` then `main.sh` called directly Fixes #45 --- CHANGELOG.md | 5 +++ FAQ.md | 21 +++++++++++ README.md | 6 ++-- main.sh | 69 +++++++++++++++++++++++------------- test/acceptance.sh | 6 ++-- test/fixture/main-help.stdio | 1 + test/style.pl | 6 ++++ 7 files changed, 85 insertions(+), 29 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 58fbc84..06b3bbc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,11 @@ Released: Unreleased. [Commit log](https://github.com/kvz/bash3boilerplate/compare/v2.2.0...master) +- Add magic variable `__i_am_main_script` to distinguish if b3bp is being sourced or called directly (#45, @zbeekman) +- Add style checks for tab characters and trailing whitespace (@zbeekman) +- Add backtracing to help localize errors (#44, @zbeekman) +- Additional FAQ entries (#47, suggested by @gdevenyi, implemented by @zbeekman) +- Ensure that shifting over `--` doesn't throw an errexit error (#21, @zbeekman) - Add Pull Request template (#83) ## v2.2.0 diff --git a/FAQ.md b/FAQ.md index 1f0b26d..de6aff6 100644 --- a/FAQ.md +++ b/FAQ.md @@ -16,6 +16,8 @@ * [You are saying you are portable, but why won't b3bp code run in dash / busybox / posh / ksh / mksh / zsh](#you-are-saying-you-are-portable-but-why-wont-b3bp-code-run-in-dash--busybox--posh--ksh--mksh--zsh)? * [How do I do Operating System detection](#how-do-i-do-operating-system-detection)? * [How do I access a potentially unset (environment) variable](#how-do-i-access-a-potentially-unset-environment-variable)? +* [How can I detect or trap CTRL-C and other signals](#how-can-i-detect-or-trap-ctrl-c-and-other-signals)? +* [How can I get the PID of my running script](how-can-i-get-the-pid-of-my-running-script)? @@ -172,3 +174,22 @@ NAME3=${NAME3:-Damian}; echo ${NAME3} # echos Damian, $NAME3 is set to Damian ``` This subject is briefly touched on as well in the [Safety and Portability section under point 5](README.md#safety-and-portability). b3bp currently uses [method 1](https://github.com/kvz/bash3boilerplate/blob/v2.1.0/main.sh#L252) when we want to access a variable that could be undeclared, and [method 3](https://github.com/kvz/bash3boilerplate/blob/v2.1.0/main.sh#L31) when we also want to set a default to an undeclared variable, because we feel it is more readable than method 2. We feel `:=` is easily overlooked, and not very beginner friendly. Method 3 seems more explicit in that regard in our humble opinion. + +## How can I detect or trap Ctrl-C and other signals? + +You can trap [Unix signals](https://en.wikipedia.org/wiki/Unix_signal) like [Ctrl-C](https://en.wikipedia.org/wiki/Control-C) with code similar to: + +```bash +# trap ctrl-c and call ctrl_c() +trap ctrl_c INT + +function ctrl_c() { + echo "** Trapped CTRL-C" +} +``` + +See http://mywiki.wooledge.org/SignalTrap for a list of signals, examples, and an in depth discussion. + +## How can I get the PID of my running script? + +The PID of a running script is contained in the `${$}` variable. This is *not* the pid of any subshells. With Bash 4 you can get the PID of your subshell with `${BASHPID}`. For a comprehensive list of Bash built in variables see, e.g., http://www.tldp.org/LDP/abs/html/internalvariables.html diff --git a/README.md b/README.md index 831a4bc..e404a13 100644 --- a/README.md +++ b/README.md @@ -126,9 +126,11 @@ $ my_script some more args --blah ### Coding style -1. Use two spaces for tabs. +1. Use two spaces for tabs, do not use tab characters. +1. Do not introduce whitespace at the end of lines or on blank lines as they obfuscate version control diffs. 1. Use long options (`logger --priority` vs `logger -p`). If you are on the CLI, abbreviations make sense for efficiency. Nevertheless, when you are writing reusable scripts, a few extra keystrokes will pay off in readability and avoid ventures into man pages in the future, either by you or your collaborators. Similarly, we prefer `set -o nounset` over `set -u`. -1. Use a single equal sign when checking `if [ "${NAME}" = "Kevin" ]`; double or triple signs are not needed. +1. Use a single equal sign when checking `if [[ "${NAME}" = "Kevin" ]]`; double or triple signs are not needed. +1. Use the new bash builtin test operator (`[[ ... ]]`) rather than the old single square bracket test operator or explicit call to `test`. ### Safety and Portability diff --git a/main.sh b/main.sh index 3861f7e..67a00b4 100755 --- a/main.sh +++ b/main.sh @@ -40,6 +40,11 @@ fi __dir="$(cd "$(dirname "${BASH_SOURCE[${__b3bp_tmp_source_idx:-0}]}")" && pwd)" __file="${__dir}/$(basename "${BASH_SOURCE[${__b3bp_tmp_source_idx:-0}]}")" __base="$(basename "${__file}" .sh)" +if [[ "${BASH_SOURCE[0]}" != "${0}" ]]; then + __i_am_main_script="0" # false +else + __i_am_main_script="1" # true +fi # Define the environment variables (and their defaults) that this script depends on LOG_LEVEL="${LOG_LEVEL:-6}" # 7 = debug -> 0 = emergency @@ -194,8 +199,8 @@ while read -r __b3bp_tmp_line; do else __b3bp_tmp_re="^'(.*)'$" if [[ "${__b3bp_tmp_init}" =~ ${__b3bp_tmp_re} ]]; then - __b3bp_tmp_init="${BASH_REMATCH[1]}" - fi + __b3bp_tmp_init="${BASH_REMATCH[1]}" + fi fi fi fi @@ -218,7 +223,7 @@ if [[ "${__b3bp_tmp_opts:-}" ]]; then # start parsing command line set +o nounset # unexpected arguments will cause unbound variables - # to be dereferenced + # to be dereferenced # Overwrite $arg_ defaults with the actual CLI options while getopts "${__b3bp_tmp_opts}" __b3bp_tmp_opt; do [[ "${__b3bp_tmp_opt}" = "?" ]] && help "Invalid use of script: ${*} " @@ -226,22 +231,22 @@ if [[ "${__b3bp_tmp_opts:-}" ]]; then if [[ "${__b3bp_tmp_opt}" = "-" ]]; then # OPTARG is long-option-name or long-option=value if [[ "${OPTARG}" =~ .*=.* ]]; then - # --key=value format - __b3bp_tmp_long_opt=${OPTARG/=*/} - # Set opt to the short option corresponding to the long option - __b3bp_tmp_varname="__b3bp_tmp_opt_long2short_${__b3bp_tmp_long_opt//-/_}" - printf -v "__b3bp_tmp_opt" '%s' "${!__b3bp_tmp_varname}" - OPTARG=${OPTARG#*=} + # --key=value format + __b3bp_tmp_long_opt=${OPTARG/=*/} + # Set opt to the short option corresponding to the long option + __b3bp_tmp_varname="__b3bp_tmp_opt_long2short_${__b3bp_tmp_long_opt//-/_}" + printf -v "__b3bp_tmp_opt" '%s' "${!__b3bp_tmp_varname}" + OPTARG=${OPTARG#*=} else - # --key value format - # Map long name to short version of option - __b3bp_tmp_varname="__b3bp_tmp_opt_long2short_${OPTARG//-/_}" - printf -v "__b3bp_tmp_opt" '%s' "${!__b3bp_tmp_varname}" - # Only assign OPTARG if option takes an argument - __b3bp_tmp_varname="__b3bp_tmp_has_arg_${__b3bp_tmp_opt}" - printf -v "OPTARG" '%s' "${@:OPTIND:${!__b3bp_tmp_varname}}" - # shift over the argument if argument is expected - ((OPTIND+=__b3bp_tmp_has_arg_${__b3bp_tmp_opt})) + # --key value format + # Map long name to short version of option + __b3bp_tmp_varname="__b3bp_tmp_opt_long2short_${OPTARG//-/_}" + printf -v "__b3bp_tmp_opt" '%s' "${!__b3bp_tmp_varname}" + # Only assign OPTARG if option takes an argument + __b3bp_tmp_varname="__b3bp_tmp_has_arg_${__b3bp_tmp_opt}" + printf -v "OPTARG" '%s' "${@:OPTIND:${!__b3bp_tmp_varname}}" + # shift over the argument if argument is expected + ((OPTIND+=__b3bp_tmp_has_arg_${__b3bp_tmp_opt})) fi # we have set opt/OPTARG to the short value and the argument as OPTARG if it exists fi @@ -260,7 +265,9 @@ if [[ "${__b3bp_tmp_opts:-}" ]]; then shift $((OPTIND-1)) - [[ "${1:-}" = "--" ]] && shift + if [[ "${1:-}" = "--" ]] ; then + shift + fi fi @@ -301,6 +308,23 @@ if [[ "${__b3bp_external_usage:-}" = "true" ]]; then return fi +### Signal trapping and backtracing +############################################################################## + +function __b3bp_cleanup_before_exit () { + info "Cleaning up. Done" +} +trap __b3bp_cleanup_before_exit EXIT + +# requires `set -o errtrace` +__b3bp_err_report() { + local error_code + error_code=${?} + error "Error in ${__file} in function ${1} on line ${2}" + exit ${error_code} +} +# Uncomment the following line for always providing an error backtrace +# trap '__b3bp_err_report "${FUNCNAME:-.}" ${LINENO}' ERR ### Command-line argument switches (like -d for debugmode, -h for showing helppage) ############################################################################## @@ -309,6 +333,8 @@ fi if [[ "${arg_d:?}" = "1" ]]; then set -o xtrace LOG_LEVEL="7" + # Enable error backtracing + trap '__b3bp_err_report "${FUNCNAME:-.}" ${LINENO}' ERR fi # verbose mode @@ -338,11 +364,6 @@ fi ### Runtime ############################################################################## -function cleanup_before_exit () { - info "Cleaning up. Done" -} -trap cleanup_before_exit EXIT - info "__file: ${__file}" info "__dir: ${__dir}" info "__base: ${__base}" diff --git a/test/acceptance.sh b/test/acceptance.sh index 59cc586..33565d8 100755 --- a/test/acceptance.sh +++ b/test/acceptance.sh @@ -208,9 +208,9 @@ if [[ "$(command -v shellcheck)" ]]; then if ! shellcheck --shell=bash --external-sources --color=always \ "${file}" >> "${__accptstTmpDir}/shellcheck.err"; then - echo "✗" - failed="true" - continue + echo "✗" + failed="true" + continue fi echo "✓" diff --git a/test/fixture/main-help.stdio b/test/fixture/main-help.stdio index 9c31d7a..73ee7d9 100644 --- a/test/fixture/main-help.stdio +++ b/test/fixture/main-help.stdio @@ -14,3 +14,4 @@ ACCPTST:STDIO_REPLACE_DATETIMES program or elaborate more on command-line arguments. This section is not parsed and will be added as-is to the help. +{datetime} UTC [ info] Cleaning up. Done diff --git a/test/style.pl b/test/style.pl index d8f6a16..9dffcb5 100755 --- a/test/style.pl +++ b/test/style.pl @@ -30,6 +30,12 @@ while (<$fh>) { # highlight double equal sign $errors += s/(\[\[.*)(==)(.*\]\])/$1\033[31m$2\033[0m$3/g; + # highlight tabs mixed with whitespace at beginning of lines + $errors += s/^( *)(\t+ *)/\033[31m\[$2\]\033[0m/; + + # highlight trailing whitespace + $errors += s/([ \t]+)$/\033[31m\[$1\]\033[0m/; + next if (not $errors); print "${file}[$.]: $_"; $rc = 1;