Skip to content

Commit 374125b

Browse files
committed
fix(parser): expand variables in [[ =~ $var ]] regex patterns
When regex pattern after =~ operator contains a variable reference ($), use parse_word() for proper expansion instead of collect_conditional_regex_pattern() which creates literal words. Literal regex patterns (without $) still use the special collector to preserve parens, backslashes, etc. Closes #400 https://claude.ai/code/session_01WZjYqxm5xMPAEe7FSHJkDy
1 parent fc51ce0 commit 374125b

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
@@ -9686,4 +9686,26 @@ bash /tmp/opts.sh -f xml -v
96869686
assert_eq!(lines[0], "FORMAT=json VERBOSE=1");
96879687
assert_eq!(lines[1], "FORMAT=xml VERBOSE=1");
96889688
}
9689+
9690+
#[tokio::test]
9691+
async fn test_regex_match_from_variable() {
9692+
// Issue #400: [[ =~ $var ]] should work with regex from variable
9693+
let mut bash = crate::Bash::new();
9694+
let result = bash
9695+
.exec(r#"re="200"; line="hello 200 world"; [[ $line =~ $re ]] && echo "match" || echo "no""#)
9696+
.await
9697+
.unwrap();
9698+
assert_eq!(result.stdout.trim(), "match");
9699+
}
9700+
9701+
#[tokio::test]
9702+
async fn test_regex_match_literal() {
9703+
// Issue #400: literal regex should still work
9704+
let mut bash = crate::Bash::new();
9705+
let result = bash
9706+
.exec(r#"line="hello 200 world"; [[ $line =~ 200 ]] && echo "match" || echo "no""#)
9707+
.await
9708+
.unwrap();
9709+
assert_eq!(result.stdout.trim(), "match");
9710+
}
96899711
}

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)