Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save brlin-tw/153df2fc2d62fb1c64c5fd1ce2be554c to your computer and use it in GitHub Desktop.
Save brlin-tw/153df2fc2d62fb1c64c5fd1ce2be554c to your computer and use it in GitHub Desktop.
Source'd parameter definition in AND LIST in bash script isn't find afterwards - Stack Overflow
#!/usr/bin/env bash
declare -r APPLICATION_NAME='Clean Filter for GNU Bash Scripts'
# 林博仁 © 2017, 2018
# NOTE: ALWAYS PRINT MESSAGES TO STDERR as output to stdout will contaminate the input files when the program is operate in filter mode.
## Makes debuggers' life easier - Unofficial Bash Strict Mode
## BASHDOC: Shell Builtin Commands - Modifying Shell Behavior - The Set Builtin
set -o errexit
set -o errtrace
set -o nounset
set -o pipefail
## Runtime Dependencies Checking
declare\
runtime_dependency_checking_result=still-pass\
required_software
for required_command in \
basename \
dirname \
realpath; do
if ! command -v "${required_command}" &>/dev/null; then
runtime_dependency_checking_result=fail
case "${required_command}" in
basename \
|dirname \
|realpath)
required_software='GNU Coreutils'
;;
*)
required_software="${required_command}"
;;
esac
printf -- \
'Error: This program requires "%s" to be installed and its executables in the executable searching paths.\n' \
"${required_software}" \
1>&2
unset required_software
fi
done; unset required_command required_software
if [ "${runtime_dependency_checking_result}" = fail ]; then
printf --\
'Error: Runtime dependency checking fail, the progrom cannot continue.\n' 1>&2
exit 1
fi; unset runtime_dependency_checking_result
## Non-overridable Primitive Variables
## BASHDOC: Shell Variables » Bash Variables
## BASHDOC: Basic Shell Features » Shell Parameters » Special Parameters
if [ -v 'BASH_SOURCE[0]' ]; then
RUNTIME_EXECUTABLE_PATH="$(realpath --strip "${BASH_SOURCE[0]}")"
RUNTIME_EXECUTABLE_FILENAME="$(basename "${RUNTIME_EXECUTABLE_PATH}")"
RUNTIME_EXECUTABLE_NAME="${RUNTIME_EXECUTABLE_FILENAME%.*}"
RUNTIME_EXECUTABLE_DIRECTORY="$(dirname "${RUNTIME_EXECUTABLE_PATH}")"
RUNTIME_COMMANDLINE_BASECOMMAND="${0}"
# We intentionally leaves these variables for script developers
# shellcheck disable=SC2034
declare -r \
RUNTIME_EXECUTABLE_PATH \
RUNTIME_EXECUTABLE_FILENAME \
RUNTIME_EXECUTABLE_NAME \
RUNTIME_EXECUTABLE_DIRECTORY \
RUNTIME_COMMANDLINE_BASECOMMAND
fi
declare -ar RUNTIME_COMMANDLINE_ARGUMENTS=("${@}")
## Global Variables
### Temporary file used in converter mode
### This parameter will be dropped in exit trap as we need to clean the temporary file
declare converter_intermediate_file
## init function: entrypoint of main program
## This function is called near the end of the file,
## with the script's command-line parameters as arguments
init(){
local cleaner=bashbeautify
local cleaner_basecommand='bashbeautify.py'
local flag_converter_mode=false
local -a input_files=()
if ! process_commandline_arguments \
cleaner \
flag_converter_mode \
input_files; then
printf --\
'Error: Invalid command-line parameters.\n'\
1>&2
# separate error message and help message
printf '\n' \
1>&2
print_help
exit 1
fi
if ! check_optional_dependencies \
"${cleaner}" \
cleaner_basecommand \
"${RUNTIME_EXECUTABLE_DIRECTORY}"; then
printf -- \
'Error: Unable to locate the cleaner. Please ensure that the selected cleaner is installed and its executable path is in the executable search PATHs.\n' \
1>&2
exit 1
fi
case "${flag_converter_mode}" in
false)
# Filter mode
printf -- \
'%s: Cleaning GNU Bash script...\n' \
"${APPLICATION_NAME}" \
1>&2
pass_over_filter\
"${cleaner}" \
"${cleaner_basecommand}"
;;
true)
converter_intermediate_file="$(
mktemp\
--tmpdir\
--suffix=.v\
"${APPLICATION_NAME}.XXXX"
)"
for input_file in "${input_files[@]}"; do
printf -- \
'%s: Cleaning "%s"...\n' \
"${APPLICATION_NAME}" \
"${input_file}" \
1>&2
pass_over_filter \
"${cleaner}" \
"${cleaner_basecommand}" \
<"${input_file}" \
>"${converter_intermediate_file}"
cp \
--force \
"${converter_intermediate_file}" \
"${input_file}"
done; unset input_file
;;
*)
printf -- \
"FATAL: Shouldn't be here, report bug.\\n" \
1>&2
exit 1
;;
esac
exit 0
}; declare -fr init
print_help(){
# shellcheck disable=SC2016
# Backticks(`) in this context are Markdown code formatting, not command expansion
# BASH_MANUAL: Basic Shell Features > Shell Commands > Compound Commands > Grouping Commands
{
printf '# Help Information for %s #\n' \
"${APPLICATION_NAME}"
printf '## Synopsis ##\n'
printf '### Filter Mode(default) ###\n'
printf '`cat _verilog_file_ | "%s" > _beautified_verilog_file_`\n' \
"${RUNTIME_COMMANDLINE_BASECOMMAND}"
printf '\n'
printf '(Input should be provided through data redirection by shell facility, cleaned product is provided through stdout)\n'
printf '\n'
printf '### Converter Mode ###\n'
printf '`"%s" --converter _verilog_file_ ...`\n' \
"${RUNTIME_COMMANDLINE_BASECOMMAND}"
printf '\n'
printf '## Command-line Options ##\n'
printf '### `--help` / `-h` ###\n'
printf 'This message\n\n'
printf '### `--debug` / `-d` ###\n'
printf 'Enable debug mode\n\n'
printf '### `--cleaner` / `-c` <name> ###\n'
printf 'Select cleaner: `bashbeautify`(default)\n\n'
printf '### `--converter` / `-C` ###\n'
printf 'Operate in converter mode instead of filter mode, accept non-option arguments as input files\n\n'
printf '### `--` ###\n'
printf 'Signals that further command-line arguments are all input files\n\n'
} 1>&2
return 0
}; declare -fr print_help;
process_commandline_arguments() {
local -n cleaner_ref="${1}"; shift
local -n flag_converter_mode_ref="${1}"; shift
local -n input_files_ref="${1}"
if [ "${#RUNTIME_COMMANDLINE_ARGUMENTS[@]}" -eq 0 ]; then
return 0
fi
# modifyable parameters for parsing by consuming
local -a parameters=("${RUNTIME_COMMANDLINE_ARGUMENTS[@]}")
# Normally we won't want debug traces to appear during parameter parsing, so we add this flag and defer its activation till returning(Y: Do debug)
local enable_debug=N
while true; do
if [ "${#parameters[@]}" -eq 0 ]; then
break
else
case "${parameters[0]}" in
--help\
|-h)
print_help;
exit 0
;;
--debug\
|-d)
enable_debug=Y
;;
--cleaner\
|-c)
if [ "${#parameters[@]}" -eq 1 ]; then
printf -- \
'%s: Error: --cleaner requires 1 additional argument.\n' \
"${FUNCNAME[0]}" \
1>&2
return 1
fi
cleaner_ref="${parameters[1]}"
# shift array by 1 = unset 1st then repack
unset 'parameters[0]'
if [ "${#parameters[@]}" -ne 0 ]; then
parameters=("${parameters[@]}")
fi
;;
--converter\
|-C)
flag_converter_mode_ref=true
;;
--)
# shift array by 1 = unset 1st then repack
unset 'parameters[0]'
if [ "${#parameters[@]}" -ne 0 ]; then
parameters=("${parameters[@]}")
fi
input_files_ref=("${input_files_ref[@]}" "${parameters[@]}")
# Break out loop as all arguments are processed
break
;;
*)
# Assuming converter mode
input_files_ref+=("${parameters[0]}")
;;
esac
# shift array by 1 = unset 1st then repack
unset 'parameters[0]'
if [ "${#parameters[@]}" -ne 0 ]; then
parameters=("${parameters[@]}")
fi
fi
done
if [ "${flag_converter_mode_ref}" = false ] && [ "${#input_files_ref[@]}" -ne 0 ]; then
printf -- \
'%s: Error: Only in --converter mode can have non-option arguments.\n' \
"${FUNCNAME[0]}" \
1>&2
return 1
fi
if [ "${flag_converter_mode_ref}" = true ] && [ "${#input_files_ref[@]}" -eq 0 ]; then
printf -- \
'%s: Error: No input files are supplied.\n' \
"${FUNCNAME[0]}" \
1>&2
return 1
fi
case "${cleaner_ref}" in
bashbeautify)
:
;;
*)
printf -- \
'%s: Error: --cleaner not supported.\n' \
"${FUNCNAME[0]}" \
1>&2
return 1
;;
esac
if [ "${enable_debug}" = Y ]; then
trap 'trap_return "${FUNCNAME[0]}"' RETURN
set -o xtrace
fi
return 0
}; declare -fr process_commandline_arguments
check_optional_dependencies(){
local -r cleaner="${1}"; shift
local -n cleaner_basecommand_ref="${1}"; shift
local -r runtime_executable_directory="${1}"
case "${cleaner}" in
bashbeautify)
declare -r SHC_PREFIX_DIR="${runtime_executable_directory}"
# Out of scope
# shellcheck source=/dev/null
if ! (
source "${runtime_executable_directory}/SOFTWARE_DIRECTORY_CONFIGURATION.source" \
&& source "${SDC_CODE_FORMATTERS_DIR}/SOFTWARE_DIRECTORY_CONFIGURATION.source" \
&& test -v SDC_BASHBEAUTIFY_DIR
); then
return 1
fi
cleaner_basecommand_ref="${SDC_BASHBEAUTIFY_DIR}/bashbeautify.py"
;;
*)
printf -- \
"%s: FATAL: Shouldn't be here, report bug.\\n" \
"${FUNCNAME[0]}" \
1>&2
;;
esac
if ! command -v "${cleaner_basecommand_ref}" 1>/dev/null 2>&1; then
return 1
fi
return 0
}; declare -fr check_optional_dependencies
pass_over_filter(){
local -r cleaner="${1}"; shift
local -r cleaner_basecommand="${1}"
case "${cleaner}" in
bashbeautify)
"${cleaner_basecommand}" \
--tab-str ' ' \
--tab-size 1 \
- # stdin
return 0
;;
*)
printf -- \
'%s: Error: Unsupported cleaner "%s".\n' \
"${FUNCNAME[0]}" \
"${cleaner}" \
1>&2
return 1
;;
esac
}; declare -fr pass_over_filter
## Traps: Functions that are triggered when certain condition occurred
## Shell Builtin Commands » Bourne Shell Builtins » trap
trap_errexit(){
printf 'An error occurred and the script is prematurely aborted\n' 1>&2
return 0
}; declare -fr trap_errexit; trap trap_errexit ERR
trap_exit(){
# Clean up temp files if available
if test -v converter_intermediate_file; then
if ! rm \
"${converter_intermediate_file}"; then
printf -- \
'%s: Error: Unable to remove the temporary file.\n' \
"${FUNCNAME[0]}" \
1>&2
return 1
fi
unset converter_intermediate_file
fi
return 0
}; declare -fr trap_exit; trap trap_exit EXIT
trap_return(){
local returning_function="${1}"
printf \
'DEBUG: %s: returning from %s\n' \
"${FUNCNAME[0]}" \
"${returning_function}" \
1>&2
}; declare -fr trap_return
trap_interrupt(){
printf '\n' # Separate previous output
printf \
'Recieved SIGINT, script is interrupted.' \
1>&2
return 1
}; declare -fr trap_interrupt; trap trap_interrupt INT
init "${@}"
## This script is based on the GNU Bash Shell Script Template project
## https://github.com/Lin-Buo-Ren/GNU-Bash-Shell-Script-Template
## and is based on the following version:
## GNU_BASH_SHELL_SCRIPT_TEMPLATE_VERSION="v3.0.15"
## You may rebase your script to incorporate new features and fixes from the template
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment