-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathshell-boilerplate.sh
More file actions
executable file
·318 lines (294 loc) · 9.46 KB
/
shell-boilerplate.sh
File metadata and controls
executable file
·318 lines (294 loc) · 9.46 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
#!/usr/bin/env sh
#
# FIXME Script for reasons
#
# SPDX-FileCopyrightText: 2026, foundata GmbH (https://foundata.com)
# SPDX-License-Identifier: GPL-3.0-or-later
# --- BOILERPLATE START v1.1.0 ---
# Consistent environment for predictable tool and shell behavior
export PATH="${PATH:-'/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin'}"
if command -v locale >/dev/null 2>&1; then
for locale_candidate in 'C.UTF-8' 'C.utf8' 'en_US.UTF-8' 'UTF-8' 'C'; do
if LC_ALL="${locale_candidate}" locale charmap >/dev/null 2>&1; then
export LC_ALL="${locale_candidate}"
break
fi
done
else
export LC_ALL='C'
fi
readonly LC_ALL
unset locale_candidate
set -u # no uninitialized vars
set -o 2>/dev/null | grep -Fq 'pipefail' && set +o pipefail # disable, non-POSIX
# Config msg() messages (override via environment or inline where needed)
: "${DEBUG:=0}" # 0: No debug messages. 1: Print debug messages.
: "${MSG_TIMESTAMP:=0}" # 0: No timestamp (TS) prefix 1: Unix TS. 2: ISO TS
: "${MSG_SCRIPTNAME:=0}" # 0: No scriptname prefix. 1: Enable scriptname prefix
# Formatting codes (ANSI if STDOUT is TTY and NO_COLOR empty; empty otherwise)
if [ -t 1 ] && [ -z "${NO_COLOR:-}" ]; then
x1b=$(printf '\033') # escape byte (0x1b) (shown as ^[ in most editors)
# terminfo|termcap comments for reference; alternative for ancient systems:
# FMT_FOO="$(tput terminfo_foo 2>/dev/null || tput termcap_foo 2>/dev/null)"
FMT_RESET="${x1b}(B${x1b}[m" # sgr0|me (G0 charset/US-ASCII, attributes reset)
FMT_BOLD="${x1b}[1m" # bold|md
FMT_UL="${x1b}[4m" # smul|us
FMT_SO="${x1b}[7m" # smso|so (standout, reverse video)
FMT_RED="${x1b}[31m" # setaf N|AF N (1=red)
FMT_GREEN="${x1b}[32m" # setaf N|AF N (2=green)
FMT_YELLOW="${x1b}[33m" # setaf N|AF N (3=yellow)
FMT_BLUE="${x1b}[34m" # setaf N|AF N (4=blue)
unset x1b
else
FMT_RESET='' FMT_BOLD='' FMT_UL='' FMT_SO='' FMT_RED='' FMT_GREEN='' FMT_YELLOW='' FMT_BLUE=''
fi
# shellcheck disable=SC2034 # boilerplate vars, needed "on stockpile"
readonly FMT_RESET FMT_BOLD FMT_UL FMT_SO FMT_RED FMT_GREEN FMT_YELLOW FMT_BLUE
###
# Print formatted messages to STDOUT or STDERR.
# Options:
# -e, --error Print error message (bold red) to STDERR.
# -w, --warning Print warning message (bold yellow) to STDERR.
# -s, --success Print success message (bold green) to STDOUT.
# -i, --info Print info message (bold blue) to STDOUT.
# -d, --debug Print debug message (standout) to STDOUT (only if DEBUG=1).
# Globals:
# DEBUG - If 0, suppresses -d/--debug messages.
# MSG_TIMESTAMP - 1: Enable unix timestamp as prefix.
# 2: Enable ISO timestamp as prefix.
# MSG_SCRIPTNAME - 1: Enable script name as prefix.
# Arguments:
# $1 - Optional flag (see "Options").
# $@ - Message to print.
# Outputs:
# Formatted message to STDOUT or STDERR depending on flag.
msg() {
local _msg_fd='1' _msg_color='' _msg_prefix='' _msg_fmt=''
case "${1:-}" in
'-e' | '--error')
_msg_fd='2'
_msg_color="${FMT_BOLD}${FMT_RED}"
;;
'-w' | '--warning')
_msg_fd='2'
_msg_color="${FMT_BOLD}${FMT_YELLOW}"
;;
'-s' | '--success')
_msg_fd='1'
_msg_color="${FMT_BOLD}${FMT_GREEN}"
;;
'-i' | '--info')
_msg_fd='1'
_msg_color="${FMT_BOLD}${FMT_BLUE}"
;;
'-d' | '--dbg' | '--debug')
[ "${DEBUG:-0}" = 0 ] && return 0
_msg_fd='1'
_msg_color="${FMT_SO}"
;;
*) false ;;
esac && shift
case "${MSG_TIMESTAMP:-0}" in
'1') _msg_prefix="[$(date '+%s')] " ;; # non-POSIX but widely available: %s
'2') _msg_prefix="[$(date '+%Y-%m-%dT%H:%M:%S%z')] " ;; # non-POSIX but widely available: z
*) ;;
esac
case "${MSG_SCRIPTNAME:-0}" in
'1') _msg_prefix="[${0##*/}] ${_msg_prefix}" ;;
*) ;;
esac
_msg_fmt="${_msg_color}${_msg_prefix}$*${FMT_RESET}"
[ "${_msg_fd}" = '2' ] && printf '%s\n' "${_msg_fmt}" >&2 || printf '%s\n' "${_msg_fmt}"
}
###
# Manage cleanup commands on exit/interrupt (LIFO order).
# Globals:
# _TRAP_STACK - Newline-separated list of commands (newest first).
# Modified by push/pop/run operations.
# Arguments:
# $1 - Action: push (add to stack), pop (remove last (no execute)),
# or run (execute all & clear).
# $2 - Command to register (required for push).
# Returns:
# 0 on success, 1 on invalid usage.
# Example:
# trap_stack push 'rm -rf "/tmp/mydir"'
# trap_stack pop
# trap_stack run
_TRAP_STACK=''
trap_stack() {
case "${1:-}" in
'push')
# linebreak is needed (stack delimiter)
_TRAP_STACK="${2:?Command required}${_TRAP_STACK:+
${_TRAP_STACK}}"
trap 'trap_stack run' EXIT
trap 'trap_stack run; exit 130' INT
trap 'trap_stack run; exit 143' TERM
;;
'pop')
_TRAP_STACK="$(printf '%s\n' "${_TRAP_STACK}" | tail -n +2)"
[ -z "${_TRAP_STACK}" ] && trap - EXIT INT TERM
;;
'run')
while [ -n "${_TRAP_STACK}" ]; do
eval "$(printf '%s\n' "${_TRAP_STACK}" | head -n 1)" || true
_TRAP_STACK="$(printf '%s\n' "${_TRAP_STACK}" | tail -n +2)"
done
trap - EXIT INT TERM
;;
*)
printf 'Usage: trap_stack push|pop|run [cmd]\n' >&2
return 1
;;
esac
}
###
# Check if commands are available.
# Options:
# -r Required mode: exit with error if any command is missing.
# Arguments:
# $@ - Command names to check.
# Returns:
# 0 if all commands exist.
# 1 if any missing (or exit 1 if -r is set)
check_cmd() {
local required=0
[ "${1}" = "-r" ] && required=1 && shift
for cmd; do
command -v "${cmd}" >/dev/null 2>&1 && continue
[ "${required}" = 1 ] || return 1
msg -e "Required command not found: ${cmd}"
exit 1
done
}
###
# Run a command that should never fail. If the command fails, print an error
# and exit immediately.
# Arguments:
# $@ - Command and arguments to execute.
# Outputs:
# Error message to STDERR on failure.
# Returns:
# 0 on success.
# >0 (the original exitcode of the command) on failure.
ensure() {
local exit_code
"$@"
exit_code="$?"
if [ "${exit_code}" -ne 0 ]; then
msg -e "Command failed (exit code ${exit_code}): $*"
exit "${exit_code}"
fi
return 0
}
# Convenience wrappers (see the used functions for documentation)
require_cmd() { check_cmd -r "$@"; }
# --- BOILERPLATE END v1.1.0 ---
###### FIXME additional /optional but often useful boilerplate follow
###
# Parse command line arguments.
# Globals:
# opt_bar - Set to 1 if -b is given.
# opt_foo - Set to value of -f argument.
# Arguments:
# $@ - Command line arguments.
# Returns:
# 0 on success, 2 on usage error.
parse_args() {
opt_bar='0'
opt_foo=''
# getopts format string: '^:' silence STDERR, 'x:' needs value, 'x' is a flag
OPTIND='1' OPTARG='' OPT=''
while getopts ':bf:h' OPT; do
case "${OPT}" in
# FIXME short desc of the flag
'b')
opt_bar='1'
;;
# FIXME short desc of the parameter
'f')
opt_foo="${OPTARG}"
if ! printf '%s' "${opt_foo}" | grep -E -q -e '^[[:digit:]]*$'; then
opt_foo=''
msg -w "Invalid value for '-${OPT}', ignoring it."
fi
;;
# show help
'h')
filename="${0##*/}"
# <<- allows tab-indentation (stripped by the shell; no spaces). The
# text is left unindented to avoid formatter/linter issues with mixed
# tabs and spaces as content uses spaces for mandoc/groff formatting.
mantext="$(
cat <<-DELIM
.TH ${filename} 1
.SH NAME
${filename} - FIXME make things for reasons.
.SH SYNOPSIS
.B ${filename}
.PP
.BI "[-f " "foo" "]"
.B [-h]
.SH DESCRIPTION
Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod
tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At
vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren,
no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit
amet, consetetur.
.SH OPTIONS
.TP
.B -b
FIXME Long desc of the flag "-b".
.TP
.B -f foo
FIXME Long desc of parameter "-f".
.TP
.B -h
Print this help.
.SH ENVIRONMENT VARIABLES
This program uses the following environment variables:
.TP
.B FIXME
Allows the specification of a default value for FIXME.
.SH EXIT STATUS
This program returns an exit status of zero if it succeeds. Non zero
is returned in case of failure. 2 will be returned for command line
syntax errors (e.g. usage of an unknown option).
.SH AUTHOR
John "FIXME" Doe <john@example.com>
DELIM
)"
if check_cmd 'mandoc'; then
printf '%s' "${mantext}" | mandoc -Tascii -man | more
elif check_cmd 'groff'; then
printf '%s' "${mantext}" | groff -Tascii -man | more
else
msg -e "Neither 'mandoc' nor 'groff' is available, cannot display help"
exit 1
fi
unset filename mantext
exit 0
;;
*)
msg -e "Unknown option '${OPTARG}' (or missing option value). Use '-h' to get usage instructions."
exit 2
;;
esac
done
unset opt OPTARG
shift $((OPTIND - 1)) && OPTIND='1' # delete processed options, reset index
}
###
# Main entry point.
# Arguments:
# $@ - Command line arguments.
main() {
trap_stack 'push' "rm -f '${tmp_file:-}'"
parse_args "$@"
# FIXME Main script logic goes here
msg "opt_bar=${opt_bar}"
msg "opt_foo=${opt_foo}"
msg -s 'Script executed successfully.'
}
main "$@"