-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathkeyval.sh
158 lines (138 loc) · 4.99 KB
/
keyval.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
# shellcheck disable=SC2148
################################################################################
# Shell functions for CRUD manipulation of key/value pairs in shell-like files
# e.g. `FOO=bar`
#
# Four functions are defined, one each for CRUD.
# One further function is defined as an `eval` wrapper.
#
# The first argument to each function is the name of the file
# The optional second argument is the key to be read or written
# The optional third argument is the value to be written.
#
# If `keyval.read` is passed no key, then it reads all key-value pairs.
#
# `keyval.import` calls `keyval.read` and uses the key-value pairs returned to
# update the corresponding variables in the calling context.
#
# `keyval.import` is safer than `source` as only `key=value` lines are processed.
################################################################################
__keyval.sedescape() {
# Escape any characters likely to confuse sed
sed -E -e 's/([][\\&./])/\\\1/g' <<< "$1"
}
keyval.read() {(
use strict
use utils
use parse-opt
parse-opt.flags "STRIP"
eval "$(parse-opt-simple)"
filename="$1"; shift
key="${1:-}"
if [ -n "$key" ]; then
regex="^\\s*${key}(\[[A-Za-z0-9_]+\])?="
else
regex="^\\s*[][A-Za-z0-9_]+="
fi
( grep -E "$regex" "$filename" || true ) | while IFS=$'\n' read -r line; do
[ -n "$line" ] || continue
key="${line%%=*}"
val="${line#*=}"
if [ "${STRIP:-}" != "false" ]; then
# Strip enclosing quotes
val=$(sed -E -e 's/^"(.*)"$/\1/' <<< "$val")
val=$(sed -E -e "s/^'(.*)'$/\1/" <<< "$val")
fi
printf "%s=%q\n" "${key}" "${val}"
done
)}
keyval-read () { keyval.read "$@" ;}
keyval.import() {
eval "$(keyval.read "$@")"
}
keyval.add() {(
use strict
use utils
use parse-opt
parse-opt.flags "UPDATE MULTI MATCH_INDENT"
eval "$(parse-opt-simple)"
filename="$1"; shift
key="$1"; shift
val="${1:-}"
keyquote=$(__keyval.sedescape "$key")
valquote=$(__keyval.sedescape "$val")
if ! grep -Eq "^\\s*${keyquote}=" "$filename" 2>/dev/null || [ "${MULTI:-}" == true ]; then
# Either there is no matching uncommented key, or we don't care
if grep -Eq "^\\s*#?\\s*${keyquote}=${valquote}\$" "$filename" 2>/dev/null; then
# If an exact match exists (commented or otherwise), forcibly uncomment it
if [ "${MATCH_INDENT:-}" == true ]; then
sed -i -e "s/^\\(\\s*\\)#\\(\\s*${keyquote}=${valquote}\\)$/\\1\\2/" "$filename"
else
sed -i -e "s/^\\s*#\\s*\\(${keyquote}=${valquote}\\)$/\\1/" "$filename"
fi
elif grep -Eq "^\\s*#?\\s*${keyquote}=" "$filename" 2>/dev/null; then
# Add above the first existing line (commented or otherwise)
# https://stackoverflow.com/a/33416489
# This matches the first instance, replaces using a repeat regex, then
# enters an inner loop that consumes the rest of the file verbatim
if [ "${MATCH_INDENT:-}" == true ]; then
sed -i -e "/^\\(\\s*\\)\\(#\?\\)\\(\\s*${keyquote}=\\)/ {
s//\\1\\3${valquote}\\n\\1\\2\\3/
:a
\$! {
n
ba
} \
}" "$filename"
else
sed -i -e "/^\\(\\s*#\?\\s*\\)\\(${keyquote}=\\)/ {
s//\\2${valquote}\\n\\1\\2/
:a
\$! {
n
ba
}
}" "$filename"
fi
else
say "${key}=${val}" >> "$filename"
fi
elif [ "${UPDATE:-}" != "false" ]; then
# There is a matching uncommented key AND the value must be modified
keyval.update --no-add "$filename" "$key" "$val"
fi
)}
keyval-add () { keyval.add "$@" ;}
keyval.update() {(
use strict
use utils
use parse-opt
parse-opt.flags "ADD MATCH_INDENT"
eval "$(parse-opt-simple)"
filename="$1"; shift
key="$1"; shift
val="${1:-}"
keyquote=$(__keyval.sedescape "$key")
valquote=$(__keyval.sedescape "$val")
sed -i -e "s/^\\(\\s*${keyquote}=\\).*$/\\1${valquote}/" "$filename"
if [ "${ADD:-}" != "false" ] && ! grep -E -q "^\\s*${keyquote}=" "$filename"; then
MATCH_INDENT="${MATCH_INDENT:-}" keyval-add --no-update "$filename" "$key" "$val"
fi
)}
keyval-update () { keyval.update "$@" ;}
keyval.delete() {(
use strict
use utils
use parse-opt
parse-opt.flags "COMMENT"
eval "$(parse-opt-simple)"
filename="$1"; shift
keyquote=$(__keyval.sedescape "$1")
if [ "${COMMENT:-}" == "true" ]; then
# comment out instead of deleting
sed -E -i -e "s/^\\s*${keyquote}(\[[A-Za-z0-9_]+\])?=/#&/" "$filename"
else
sed -E -i -e "/^\\s*${keyquote}(\[[A-Za-z0-9_]+\])?=.*$/d" "$filename"
fi
)}
keyval-delete () { keyval.delete "$@" ;}