aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorBenjamin Linskey2026-06-11 03:23:27 -0400
committerBenjamin Linskey2026-06-11 07:02:09 -0400
commitb10392c52dc06489b65e638b8ca71771b763bf03 (patch)
tree082453b9c713caf654ee3b2ad929906faec800d5
parent653365c2366785a7fb3b012fe39dbd3ca9707606 (diff)
downloadtpl-b10392c52dc06489b65e638b8ca71771b763bf03.tar.gz

Fix brace escaping in regexes

The POSIX extended regular expression spec requires that the left brace character be escaped when it appears outside of an interval expression. The results are undefined if the brace is not escaped.1

The ERE spec permits right braces to be escaped, though this isn’t required.2 But the standard ERE escaping rules are superseded in the POSIX awk specification. The awk spec does not specify a right brace escape sequence, so the meaning of an escaped right brace is undefined.3

We have a number of escaped right braces and one instance of a string variable containing unescaped left braces being passed to a function as a regex. These don’t cause any problems in practice, but to ensure maximum portability and pedantry, we bring everything into strict compliance with the spec here.

-rwxr-xr-xtpl15
1 files changed, 6 insertions, 9 deletions
diff --git a/tpl b/tpl
index 4a27c65..efd1c96 100755
--- a/tpl
+++ b/tpl
@@ -27,18 +27,15 @@ BEGIN { FS = "[[:space:]]*=[[:space:]]*" }
FILENAME == ARGV[1] && /^#/ { next }
FILENAME == ARGV[1] && NF == 2 {
# Replace template variables with previously defined values.
- while (match($2, /\{\{[[:space:]]*[^\{\}[:space:]]+[[:space:]]\}\}/)) {
+ while (match($2, /\{\{[[:space:]]*[^\{}[:space:]]+[[:space:]]}}/)) {
matched_text = substr($2, RSTART, RLENGTH)
# Extract the actual var name, without braces and space.
- match(matched_text, /[^\{\}[:space:]]+/)
+ match(matched_text, /[^\{}[:space:]]+/)
var_name = substr(matched_text, RSTART, RLENGTH)
- # Even though matched_text contains the { and } regex
- # metacharacters, we don't need to escape them because they're
- # not used in a way that matches the (r){m,n} syntax.
- # (We don't need to escape them elsewhere in this program
- # either, but we do so for maximum clarity.)
+ # Left braces must be escaped for strict POSIX compliance.
+ gsub(/\{/, "\\{", matched_text)
sub(matched_text, vals[var_name], $2)
}
@@ -46,9 +43,9 @@ FILENAME == ARGV[1] && NF == 2 {
}
FILENAME == ARGV[1] { next }
-/\{\{[[:space:]]*[^\{\}[:space:]]+[[:space:]]*\}\}/ {
+/\{\{[[:space:]]*[^\{}[:space:]]+[[:space:]]*}}/ {
for (key in vals) {
- gsub("\{\{[[:space:]]*" key "[[:space:]]*\}\}", vals[key])
+ gsub("\{\{[[:space:]]*" key "[[:space:]]*}}", vals[key])
}
}