Skip to content

Commit 6e63506

Browse files
chaliyclaude
andauthored
fix(parser): expand variables in [[ =~ $var ]] regex patterns (#482)
## Summary - Fixed `[[ $line =~ $var ]]` regex matching where `$var` was not expanded - Root cause: `collect_conditional_regex_pattern()` creates `Word::literal` bypassing variable expansion - Fix: when regex pattern contains `$`, use `parse_word()` for expansion; otherwise use literal collector to preserve parens/backslashes Closes #400 ## Test plan - [x] test_regex_match_from_variable - [x] test_regex_match_literal - [x] Spec test regex_match_in_conditional still passes - [x] All 1509 tests pass Co-authored-by: Claude <noreply@anthropic.com>
1 parent 183eaa2 commit 6e63506

File tree

2 files changed

+35
-3
lines changed

2 files changed

+35
-3
lines changed

crates/bashkit/src/interpreter/mod.rs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9727,4 +9727,26 @@ echo "count=$COUNT"
97279727
let result = bash.exec(r#"printf "a\nb\nc" | wc -l"#).await.unwrap();
97289728
assert_eq!(result.stdout.trim(), "2");
97299729
}
9730+
9731+
#[tokio::test]
9732+
async fn test_regex_match_from_variable() {
9733+
// Issue #400: [[ =~ $var ]] should work with regex from variable
9734+
let mut bash = crate::Bash::new();
9735+
let result = bash
9736+
.exec(r#"re="200"; line="hello 200 world"; [[ $line =~ $re ]] && echo "match" || echo "no""#)
9737+
.await
9738+
.unwrap();
9739+
assert_eq!(result.stdout.trim(), "match");
9740+
}
9741+
9742+
#[tokio::test]
9743+
async fn test_regex_match_literal() {
9744+
// Issue #400: literal regex should still work
9745+
let mut bash = crate::Bash::new();
9746+
let result = bash
9747+
.exec(r#"line="hello 200 world"; [[ $line =~ 200 ]] && echo "match" || echo "no""#)
9748+
.await
9749+
.unwrap();
9750+
assert_eq!(result.stdout.trim(), "match");
9751+
}
97309752
}

crates/bashkit/src/parser/mod.rs

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1263,10 +1263,20 @@ impl<'a> Parser<'a> {
12631263
let is_literal =
12641264
matches!(self.current_token, Some(tokens::Token::LiteralWord(_)));
12651265

1266-
// After =~, collect the regex pattern (may contain parens)
1266+
// After =~, handle regex pattern.
1267+
// If the pattern contains $ (variable reference), parse it as a
1268+
// normal word so variables expand. Otherwise collect as literal
1269+
// regex to preserve parens, backslashes, etc.
12671270
if saw_regex_op {
1268-
let pattern = self.collect_conditional_regex_pattern(&w_clone);
1269-
words.push(Word::literal(&pattern));
1271+
if w_clone.contains('$') && !is_quoted {
1272+
// Variable reference — parse normally for expansion
1273+
let parsed = self.parse_word(w_clone);
1274+
words.push(parsed);
1275+
self.advance();
1276+
} else {
1277+
let pattern = self.collect_conditional_regex_pattern(&w_clone);
1278+
words.push(Word::literal(&pattern));
1279+
}
12701280
saw_regex_op = false;
12711281
continue;
12721282
}

0 commit comments

Comments
 (0)