-
Notifications
You must be signed in to change notification settings - Fork 4
/
read_ini.sh
259 lines (214 loc) · 5.82 KB
/
read_ini.sh
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
#!/usr/bin/env bash
#
# Copyright (c) 2009 Kevin Porter / Advanced Web Construction Ltd
# (http://coding.tinternet.info, http://webutils.co.uk)
# Copyright (c) 2010-2014 Ruediger Meier <[email protected]>
# (https://github.com/rudimeier/)
#
# License: BSD-3-Clause, see LICENSE file
#
# Simple INI file parser.
#
# See README for usage.
#
function read_ini() {
# Be strict with the prefix, since it's going to be run through eval
function check_prefix() {
if ! [[ ${VARNAME_PREFIX} =~ ^[a-zA-Z_][a-zA-Z0-9_]*$ ]]; then
echo "read_ini: invalid prefix '${VARNAME_PREFIX}'" >&2
return 1
fi
}
function check_ini_file() {
if [ ! -r "$INI_FILE" ]; then
echo "read_ini: '${INI_FILE}' doesn't exist or not" \
"readable" >&2
return 1
fi
}
# enable some optional shell behavior (shopt)
function pollute_bash() {
if ! shopt -q extglob; then
SWITCH_SHOPT="${SWITCH_SHOPT} extglob"
fi
if ! shopt -q nocasematch; then
SWITCH_SHOPT="${SWITCH_SHOPT} nocasematch"
fi
# shellcheck disable=SC2086
shopt -q -s ${SWITCH_SHOPT}
}
# unset all local functions and restore shopt settings before returning
# from read_ini()
function cleanup_bash() {
# shellcheck disable=SC2086
shopt -q -u ${SWITCH_SHOPT}
unset -f check_prefix check_ini_file pollute_bash cleanup_bash
}
local INI_FILE=""
local INI_SECTION=""
# {{{ START Deal with command line args
# Set defaults
local BOOLEANS=1
local VARNAME_PREFIX=INI
local CLEAN_ENV=0
# {{{ START Options
# Available options:
# --boolean Whether to recognise special boolean values: ie for 'yes', 'true'
# and 'on' return 1; for 'no', 'false' and 'off' return 0. Quoted
# values will be left as strings
# Default: on
#
# --prefix=STRING String to begin all returned variables with (followed by '__').
# Default: INI
#
# First non-option arg is filename, second is section name
while [ $# -gt 0 ]; do
case $1 in
--clean | -c)
CLEAN_ENV=1
;;
--booleans | -b)
shift
BOOLEANS=$1
;;
--prefix | -p)
shift
VARNAME_PREFIX=$1
;;
*)
if [ -z "$INI_FILE" ]; then
INI_FILE=$1
else
if [ -z "$INI_SECTION" ]; then
INI_SECTION=$1
fi
fi
;;
esac
shift
done
if [ -z "$INI_FILE" ] && [ "${CLEAN_ENV}" = 0 ]; then
echo -e "Usage: read_ini [-c] [-b 0| -b 1]] [-p PREFIX] FILE" \
"[SECTION]\n or read_ini -c [-p PREFIX]" >&2
cleanup_bash
return 1
fi
if ! check_prefix; then
cleanup_bash
return 1
fi
local INI_ALL_VARNAME="${VARNAME_PREFIX}__ALL_VARS"
local INI_ALL_SECTION="${VARNAME_PREFIX}__ALL_SECTIONS"
local INI_NUMSECTIONS_VARNAME="${VARNAME_PREFIX}__NUMSECTIONS"
if [ "${CLEAN_ENV}" = 1 ]; then
eval unset "\$${INI_ALL_VARNAME}"
fi
unset "${INI_ALL_VARNAME}"
unset "${INI_ALL_SECTION}"
unset "${INI_NUMSECTIONS_VARNAME}"
if [ -z "$INI_FILE" ]; then
cleanup_bash
return 0
fi
if ! check_ini_file; then
cleanup_bash
return 1
fi
# Sanitise BOOLEANS - interpret "0" as 0, anything else as 1
if [ "$BOOLEANS" != "0" ]; then
BOOLEANS=1
fi
# }}} END Options
# }}} END Deal with command line args
local LINE_NUM=0
local SECTIONS_NUM=0
local SECTION=""
# IFS is used in "read" and we want to switch it within the loop
local IFS=$' \t\n'
local IFS_OLD="${IFS}"
# we need some optional shell behavior (shopt) but want to restore
# current settings before returning
local SWITCH_SHOPT=""
pollute_bash
while read -r line || [ -n "$line" ]; do
#echo line = "$line"
((LINE_NUM++))
# Skip blank lines and comments
if [ -z "$line" ] || [ "${line:0:1}" = ";" ] || [ "${line:0:1}" = "#" ]; then
continue
fi
# Section marker?
if [[ ${line} =~ ^\[[a-zA-Z0-9_]{1,}\]$ ]]; then
# Set SECTION var to name of section (strip [ and ] from section marker)
SECTION="${line#[}"
SECTION="${SECTION%]}"
eval "${INI_ALL_SECTION}=\"\${${INI_ALL_SECTION}# } $SECTION\""
((SECTIONS_NUM++))
continue
fi
# Are we getting only a specific section? And are we currently in it?
if [ -n "$INI_SECTION" ]; then
if [ "$SECTION" != "$INI_SECTION" ]; then
continue
fi
fi
# Valid var/value line? (check for variable name and then '=')
if ! [[ ${line} =~ ^[a-zA-Z0-9._]{1,}[[:space:]]*= ]]; then
echo "Error: Invalid line:" >&2
echo " ${LINE_NUM}: $line" >&2
cleanup_bash
return 1
fi
# split line at "=" sign
IFS="="
read -r VAR VAL <<<"${line}"
IFS="${IFS_OLD}"
# delete spaces around the equal sign (using extglob)
VAR="${VAR%%+([[:space:]])}"
VAL="${VAL##+([[:space:]])}"
# shellcheck disable=SC2116
VAR=$(echo "$VAR")
# Construct variable name:
# ${VARNAME_PREFIX}__$SECTION__$VAR
# Or if not in a section:
# ${VARNAME_PREFIX}__$VAR
# In both cases, full stops ('.') are replaced with underscores ('_')
if [ -z "$SECTION" ]; then
VARNAME=${VARNAME_PREFIX}__${VAR//./_}
else
VARNAME=${VARNAME_PREFIX}__${SECTION}__${VAR//./_}
fi
eval "${INI_ALL_VARNAME}=\"\${${INI_ALL_VARNAME}# } ${VARNAME}\""
if [[ ${VAL} =~ ^\".*\"$ ]]; then
# remove existing double quotes
VAL="${VAL##\"}"
VAL="${VAL%%\"}"
elif [[ ${VAL} =~ ^\'.*\'$ ]]; then
# remove existing single quotes
VAL="${VAL##\'}"
VAL="${VAL%%\'}"
elif [ "$BOOLEANS" = 1 ]; then
# Value is not enclosed in quotes
# Booleans processing is switched on, check for special boolean
# values and convert
# here we compare case insensitive because
# "shopt nocasematch"
case "$VAL" in
yes | true | on)
VAL=1
;;
no | false | off)
VAL=0
;;
esac
fi
# enclose the value in single quotes and escape any
# single quotes and backslashes that may be in the value
VAL="${VAL//\\/\\\\}"
VAL="\$'${VAL//\'/\'}'"
eval "$VARNAME=$VAL"
done <"${INI_FILE}"
# return also the number of parsed sections
eval "$INI_NUMSECTIONS_VARNAME=$SECTIONS_NUM"
cleanup_bash
}