-
Notifications
You must be signed in to change notification settings - Fork 1
/
simple-defaults-for-bashsteps.source
192 lines (163 loc) · 7.21 KB
/
simple-defaults-for-bashsteps.source
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
# For more documentation and a wrapper script that adds more
# functionality, refer to the following repository:
# https://github.com/axsh/bashsteps
# Sourcing this code defines and exports the following:
# $starting_step
# $skip_step_if_already_done
# $starting_group
# $skip_group_if_unnecessary
# $prev_cmd_failed
# It also exports a few depreciated items
# to keep older scripts working somewhat:
# $starting_dependents
# $starting_checks
# $skip_rest_if_already_done
# prev_cmd_failed()
# And these extras:
# reportfailed()
# Finally some experimental stuff:
# $DATADIR, $CODEDIR
# For any of these that are already defined, no change is made but the
# item is still exported. Therefore, simple subscripts steps that are
# not written to be called independently can assume these are already
# defined. (Steps that are written to be called independently will
# need to source this or some other file that defines the above exports.)
# In addition, it always sets CODEDIR, because this script should
# only be sourced by scripts that can be called independently, and
# therefore should set up their own environments. Note that if the
# calling script is started with a symbolic link, CODEDIR is set
# to the directory of the link, not final target of the link.
if ! declare -f reportfailed >/dev/null; then
reportfailed()
{
echo "Script failed...exiting. ($*)" 1>&2
exit 255
}
fi
export -f reportfailed
if ! declare -f prev_cmd_failed >/dev/null; then
prev_cmd_failed()
{
# this is needed because '( cmd1 ; cmd2 ; set -e ; cmd3 ; cmd4 ) || reportfailed'
# does not work because the || disables set -e, even inside the subshell!
# see http://unix.stackexchange.com/questions/65532/why-does-set-e-not-work-inside
# A workaround is to do '( cmd1 ; cmd2 ; set -e ; cmd3 ; cmd4 ) ; prev_cmd_failed'
(($? == 0)) || reportfailed "$*"
}
fi
export -f prev_cmd_failed
# for consistency, start to use the variable form for everything
: ${prev_cmd_failed:='eval [ $? = 0 ] || exit 255'}
export prev_cmd_failed
# According to the Bash docs, $0 is definitely unchanged if this
# script is sourced without parameters. So the path for $ORGCODEDIR
# will be the directory of the calling script, which is useful for
# accessing other scripts and files bundled with the source code.
# When the code is called with a symbolic link, $LINKCODEDIR will be
# the directory of that link, which is useful for a
# somewhat-object-oriented style of scripting that treats directories
# as objects with script links that serve as methods.
export ORGCODEDIR="$(cd "$(dirname $(readlink -f "$0"))" && pwd -P)" || reportfailed
export LINKCODEDIR="$(cd "$(dirname "$0")" && pwd -P)" || reportfailed
export CODEDIR="$LINKCODEDIR" # backwards compatibility
# A major reason for using the bashsteps framework when writing a
# script is to make it possible to partially run a script and then
# later run the rest by invoking the script again. Therefore in most
# cases, this means that state that would normally be kept in script
# variables must be saved persistently elsewhere. The purpose of
# $DATADIR is to designate a special directory for saving and
# accessing such information. There is probably no good default
# location for $DATADIR, so force the script to set it appropriately.
# Other scripts check if the first character is "/" to see if it has
# properly been set to an absolute path. So this default setting does
# not to indicate that it is not really set. It means the main
# (independent) script should overwrite it.
[ "$DATADIR" = "" ] && export DATADIR="bug/the-main-script-should-have-set-DATADIR"
# Put the current directory somewhere unwritable to force use
# of the above variables.
cd -P /proc/self
## More hints to understand the scripts that source this file:
## (1) Every step is put in its own process. The easiest way to do this
## is to use ( ), but calling other scripts is also possible for
## code reuse or readability reasons. (In addition, it is necessary
## to make sure the script terminates if *any* of the processes exit with
## an error. This is easy, but does take a little care in bash. See
## the comments in prev_cmd_failed.)
## (2) All steps start with commands that check whether the step has
## already been done. These commands should only check. They should
## not change any state.
## (3) The return code of the last "check" command is used to decide whether
## the rest of the step (i.e. the step's process) needs to be done.
## This should be done by inserting "$skip_step_if_already_done" at
## that point in the step.
## (4) An optional '$starting_step "Description of the step"' can appear
## at the start of the step.
## (5) Processes are also used to group steps into hierarchies.
## ((TODO: update the doc))
## Therefore, with minimal effort, it should be possible to take a
## the simplest easy-to-read sequential script and make it (at least
## somewhat) idempotent. In other words, it should be possible to
## run the script multiple times, and have it gracefully recover from
## failures and be able to retry efficiently after the point of failure.
## $starting_step and $skip_step_if_already_done are simple bash variables
## and can serve as hooks for controlling the script in useful ways.
## The settings below provide simple defaults that provide some log output
## and some idempotent behavior.
## The action of $starting_step as defined below is to only remember the title.
## It will be used by $skip_step_if_already_done.
## The action of $skip_step_if_already_done as defined below is to exit
## the process if the return code is 0. In addition it outputs a
## header line that reports the step's title and whether it is being
## done or skipped.
## If these variables are already defined, the code below leaves them
## unchanged. Therefore higher-level wrapper scripts can customize
## the behavior to provide more control, status updates, or debugging
## output.
## On the other extream, it is always be possible to set both variables
## to ":", which should (if everything is written correctly) turn the scripts
## back into a typical script that blindly runs from start to finish.
# depreciated:
: ${starting_dependents:=default_set_title}
: ${starting_checks:=default_set_title}
: ${skip_rest_if_already_done:=default_skip_step} # exit (sub)process if return code is 0
export starting_dependents
export starting_checks
export skip_rest_if_already_done
# the new framework:
: ${starting_step:=default_set_title}
: ${starting_group:=default_set_title}
: ${skip_step_if_already_done:=default_skip_step}
: ${skip_group_if_unnecessary:=default_skip_group}
export starting_step
export starting_group
export skip_step_if_already_done
export skip_group_if_unnecessary
default_set_title()
{
[ "$*" != "" ] && step_title="$*"
}
export -f default_set_title
default_skip_step()
{
if (($? == 0)); then
echo "** Skipping step: $step_title"
step_title=""
exit 0
else
echo ; echo "** DOING STEP: $step_title"
step_title=""
fi
}
export -f default_skip_step
default_skip_group()
{
if (($? == 0)); then
echo "** Skipping group: $step_title"
step_title=""
exit 0
else
echo ; echo "** DOING GROUP: $step_title"
step_title=""
fi
}
export -f default_skip_group