Skip to content

Commit 03941b5

Browse files
chaliyclaude
andauthored
fix(bash): BASH_REMATCH not populated when regex starts with parens (#242)
## Summary - Fix parser's parse_conditional to check saw_regex_op flag when encountering LeftParen token inside [[ ]] - Regex patterns starting with capture groups (e.g. ([0-9]+)) were incorrectly parsed as grouping parens instead of regex content - Adds 6 test cases covering basic match, multiple groups, nested groups, no-match clearing, and conditional usage ## Test plan - [x] All 6 new regex test cases pass - [x] cargo test --all-features passes - [x] bash_comparison_tests pass (validated against real bash) - [x] cargo clippy and cargo fmt clean Co-authored-by: Claude <noreply@anthropic.com>
1 parent 4807416 commit 03941b5

3 files changed

Lines changed: 81 additions & 5 deletions

File tree

crates/bashkit/src/parser/mod.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1078,6 +1078,13 @@ impl<'a> Parser<'a> {
10781078
self.advance();
10791079
}
10801080
Some(tokens::Token::LeftParen) => {
1081+
if saw_regex_op {
1082+
// Regex pattern starts with '(' — collect it
1083+
let pattern = self.collect_conditional_regex_pattern("(");
1084+
words.push(Word::literal(&pattern));
1085+
saw_regex_op = false;
1086+
continue;
1087+
}
10811088
words.push(Word::literal("("));
10821089
self.advance();
10831090
}

crates/bashkit/tests/spec_cases/bash/control-flow.test.sh

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -271,3 +271,72 @@ ERR
271271
done
272272
BYE
273273
### end
274+
275+
### regex_match_basic
276+
# [[ =~ ]] regex match returns correct exit code
277+
[[ "hello123" =~ [0-9]+ ]]; echo $?
278+
[[ "hello" =~ [0-9]+ ]]; echo $?
279+
### expect
280+
0
281+
1
282+
### end
283+
284+
### regex_match_bash_rematch
285+
# BASH_REMATCH populated with capture groups
286+
x="hello123world"
287+
[[ "$x" =~ ([0-9]+) ]]
288+
echo "${BASH_REMATCH[0]}"
289+
echo "${BASH_REMATCH[1]}"
290+
### expect
291+
123
292+
123
293+
### end
294+
295+
### regex_match_multiple_groups
296+
# Multiple capture groups in BASH_REMATCH
297+
[[ "2024-01-15" =~ ^([0-9]{4})-([0-9]{2})-([0-9]{2})$ ]]
298+
echo "${BASH_REMATCH[0]}"
299+
echo "${BASH_REMATCH[1]}"
300+
echo "${BASH_REMATCH[2]}"
301+
echo "${BASH_REMATCH[3]}"
302+
### expect
303+
2024-01-15
304+
2024
305+
01
306+
15
307+
### end
308+
309+
### regex_match_nested_groups
310+
# Nested capture groups
311+
[[ "foo123bar" =~ (foo([0-9]+)bar) ]]
312+
echo "${BASH_REMATCH[0]}"
313+
echo "${BASH_REMATCH[1]}"
314+
echo "${BASH_REMATCH[2]}"
315+
### expect
316+
foo123bar
317+
foo123bar
318+
123
319+
### end
320+
321+
### regex_match_no_match_clears
322+
# BASH_REMATCH cleared on no match
323+
[[ "abc123" =~ ([0-9]+) ]]
324+
echo "before: ${#BASH_REMATCH[@]}"
325+
[[ "abc" =~ ([0-9]+) ]]
326+
echo "after: ${#BASH_REMATCH[@]}"
327+
### expect
328+
before: 2
329+
after: 0
330+
### end
331+
332+
### regex_match_in_conditional
333+
# Regex match used in && chain
334+
x="error: line 42"
335+
if [[ "$x" =~ error:\ line\ ([0-9]+) ]]; then
336+
echo "line ${BASH_REMATCH[1]}"
337+
else
338+
echo "no match"
339+
fi
340+
### expect
341+
line 42
342+
### end

specs/009-implementation-status.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -103,16 +103,16 @@ Bashkit implements IEEE 1003.1-2024 Shell Command Language. See
103103

104104
## Spec Test Coverage
105105

106-
**Total spec test cases:** 1161 (1156 pass, 5 skip)
106+
**Total spec test cases:** 1170 (1165 pass, 5 skip)
107107

108108
| Category | Cases | In CI | Pass | Skip | Notes |
109109
|----------|-------|-------|------|------|-------|
110-
| Bash (core) | 800 | Yes | 795 | 5 | `bash_spec_tests` in CI |
110+
| Bash (core) | 809 | Yes | 804 | 5 | `bash_spec_tests` in CI |
111111
| AWK | 96 | Yes | 96 | 0 | loops, arrays, -v, ternary, field assign, getline, %.6g |
112112
| Grep | 76 | Yes | 76 | 0 | -z, -r, -a, -b, -H, -h, -f, -P, --include, --exclude, binary detect |
113113
| Sed | 75 | Yes | 75 | 0 | hold space, change, regex ranges, -E |
114114
| JQ | 114 | Yes | 114 | 0 | reduce, walk, regex funcs, --arg/--argjson, combined flags, input/inputs, env |
115-
| **Total** | **1161** | **Yes** | **1156** | **5** | |
115+
| **Total** | **1170** | **Yes** | **1165** | **5** | |
116116

117117
### Bash Spec Tests Breakdown
118118

@@ -128,15 +128,15 @@ Bashkit implements IEEE 1003.1-2024 Shell Command Language. See
128128
| command-not-found.test.sh | 17 | unknown command handling |
129129
| conditional.test.sh | 17 | `[[ ]]` conditionals, `=~` regex, BASH_REMATCH |
130130
| command-subst.test.sh | 14 | includes backtick substitution (1 skipped) |
131-
| control-flow.test.sh | 37 | if/elif/else, for, while, case, trap ERR |
131+
| control-flow.test.sh | 43 | if/elif/else, for, while, case, trap ERR, `[[ =~ ]]` BASH_REMATCH |
132132
| cuttr.test.sh | 32 | cut and tr commands, `-z` zero-terminated |
133133
| date.test.sh | 38 | format specifiers, `-d` relative/compound/epoch, `-R`, `-I`, `%N` (2 skipped) |
134134
| diff.test.sh | 4 | line diffs |
135135
| echo.test.sh | 24 | escape sequences |
136136
| errexit.test.sh | 8 | set -e tests |
137137
| fileops.test.sh | 21 | |
138138
| find.test.sh | 10 | file search |
139-
| functions.test.sh | 14 | |
139+
| functions.test.sh | 17 | local dynamic scoping, nested writes |
140140
| getopts.test.sh | 9 | POSIX option parsing, combined flags, silent mode |
141141
| globs.test.sh | 12 | for-loop glob expansion, recursive `**` |
142142
| headtail.test.sh | 14 | |

0 commit comments

Comments
 (0)