diff --git a/src/expand.rs b/src/expand.rs index 3471f6e..b28ce8c 100644 --- a/src/expand.rs +++ b/src/expand.rs @@ -1726,6 +1726,32 @@ pub fn perform_param_expansion(raw: &str) -> ShResult { } } +/// Expand a case pattern: performs variable/command expansion while preserving +/// glob metacharacters that were inside quotes as literals (by backslash-escaping them). +/// Unquoted glob chars (*, ?, [) pass through for glob_to_regex to interpret. +pub fn expand_case_pattern(raw: &str) -> ShResult { + let unescaped = unescape_str(raw); + let expanded = expand_raw(&mut unescaped.chars().peekable())?; + + let mut result = String::new(); + let mut in_quote = false; + let mut chars = expanded.chars(); + + while let Some(ch) = chars.next() { + match ch { + markers::DUB_QUOTE | markers::SNG_QUOTE => { + in_quote = !in_quote; + } + '*' | '?' | '[' | ']' if in_quote => { + result.push('\\'); + result.push(ch); + } + _ => result.push(ch), + } + } + Ok(result) +} + pub fn glob_to_regex(glob: &str, anchored: bool) -> Regex { let mut regex = String::new(); if anchored { @@ -1744,7 +1770,17 @@ pub fn glob_to_regex(glob: &str, anchored: bool) -> Regex { } '*' => regex.push_str(".*"), '?' => regex.push('.'), - '.' | '+' | '(' | ')' | '|' | '^' | '$' | '[' | ']' | '{' | '}' => { + '[' => { + // Pass through character class [...] as-is (glob and regex syntax match) + regex.push('['); + while let Some(bc) = chars.next() { + regex.push(bc); + if bc == ']' { + break; + } + } + } + '.' | '+' | '(' | ')' | '|' | '^' | '$' | '{' | '}' => { regex.push('\\'); regex.push(ch); } diff --git a/src/libsh/utils.rs b/src/libsh/utils.rs index 3141f98..9b12fe6 100644 --- a/src/libsh/utils.rs +++ b/src/libsh/utils.rs @@ -42,14 +42,17 @@ pub trait NodeVecUtils { impl AutoCmdVecUtils for Vec { fn exec(&self) { + let saved_status = crate::state::get_status(); for cmd in self { let AutoCmd { pattern: _, command } = cmd; if let Err(e) = exec_input(command.clone(), None, false, Some("autocmd".into())) { e.print_error(); } } + crate::state::set_status(saved_status); } fn exec_with(&self, other_pattern: &str) { + let saved_status = crate::state::get_status(); for cmd in self { let AutoCmd { pattern, command } = cmd; if let Some(pat) = pattern @@ -62,6 +65,7 @@ impl AutoCmdVecUtils for Vec { e.print_error(); } } + crate::state::set_status(saved_status); } } diff --git a/src/parse/execute.rs b/src/parse/execute.rs index 25d7255..1647558 100644 --- a/src/parse/execute.rs +++ b/src/parse/execute.rs @@ -9,7 +9,7 @@ use crate::{ builtin::{ alias::{alias, unalias}, arrops::{arr_fpop, arr_fpush, arr_pop, arr_push, arr_rotate}, autocmd::autocmd, cd::cd, complete::{compgen_builtin, complete_builtin}, dirstack::{dirs, popd, pushd}, echo::echo, eval, exec, flowctl::flowctl, getopts::getopts, intro, jobctl::{self, JobBehavior, continue_job, disown, jobs}, keymap, map, pwd::pwd, read::{self, read_builtin}, shift::shift, shopt::shopt, source::source, test::double_bracket_test, trap::{TrapTarget, trap}, varcmds::{export, local, readonly, unset}, zoltraak::zoltraak }, - expand::{Expander, expand_aliases, expand_raw, glob_to_regex}, + expand::{Expander, expand_aliases, expand_case_pattern, expand_raw, glob_to_regex}, jobs::{ChildProc, JobStack, attach_tty, dispatch_job}, libsh::{ error::{ShErr, ShErrKind, ShResult, ShResultExt, next_color}, @@ -428,7 +428,7 @@ impl Dispatcher { let block_patterns = block_pattern_raw.split('|'); for pattern in block_patterns { - let pattern_exp = Expander::from_raw(pattern)?.expand()?.join(" "); + let pattern_exp = expand_case_pattern(pattern)?; let pattern_regex = glob_to_regex(&pattern_exp, false); if pattern_regex.is_match(&pattern_raw) { for node in &body {