diff --git a/crates/bashkit/src/interpreter/mod.rs b/crates/bashkit/src/interpreter/mod.rs index 2a2d5823..526d18fb 100644 --- a/crates/bashkit/src/interpreter/mod.rs +++ b/crates/bashkit/src/interpreter/mod.rs @@ -6438,19 +6438,23 @@ impl Interpreter { } ParameterOp::RemovePrefixShort => { // ${var#pattern} - remove shortest prefix match - self.remove_pattern(value, operand, true, false) + let expanded = self.expand_operand(operand); + self.remove_pattern(value, &expanded, true, false) } ParameterOp::RemovePrefixLong => { // ${var##pattern} - remove longest prefix match - self.remove_pattern(value, operand, true, true) + let expanded = self.expand_operand(operand); + self.remove_pattern(value, &expanded, true, true) } ParameterOp::RemoveSuffixShort => { // ${var%pattern} - remove shortest suffix match - self.remove_pattern(value, operand, false, false) + let expanded = self.expand_operand(operand); + self.remove_pattern(value, &expanded, false, false) } ParameterOp::RemoveSuffixLong => { // ${var%%pattern} - remove longest suffix match - self.remove_pattern(value, operand, false, true) + let expanded = self.expand_operand(operand); + self.remove_pattern(value, &expanded, false, true) } ParameterOp::ReplaceFirst { pattern, diff --git a/crates/bashkit/src/parser/mod.rs b/crates/bashkit/src/parser/mod.rs index deb5db1a..67f0da9b 100644 --- a/crates/bashkit/src/parser/mod.rs +++ b/crates/bashkit/src/parser/mod.rs @@ -2534,8 +2534,38 @@ impl<'a> Parser<'a> { parts.push(WordPart::Literal(std::mem::take(&mut current))); } - // Check for $( - command substitution or arithmetic - if chars.peek() == Some(&'(') { + // Check for $'...' - ANSI-C quoting + if chars.peek() == Some(&'\'') { + chars.next(); // consume opening ' + let mut ansi = String::new(); + while let Some(c) = chars.next() { + if c == '\'' { + break; + } + if c == '\\' { + if let Some(esc) = chars.next() { + match esc { + 'n' => ansi.push('\n'), + 't' => ansi.push('\t'), + 'r' => ansi.push('\r'), + 'a' => ansi.push('\x07'), + 'b' => ansi.push('\x08'), + 'e' | 'E' => ansi.push('\x1B'), + '\\' => ansi.push('\\'), + '\'' => ansi.push('\''), + _ => { + ansi.push('\\'); + ansi.push(esc); + } + } + } + } else { + ansi.push(c); + } + } + parts.push(WordPart::Literal(ansi)); + } else if chars.peek() == Some(&'(') { + // Check for $( - command substitution or arithmetic chars.next(); // consume first '(' // Check for $(( - arithmetic expansion diff --git a/crates/bashkit/tests/spec_cases/bash/blackbox-edge-cases.test.sh b/crates/bashkit/tests/spec_cases/bash/blackbox-edge-cases.test.sh index dd5af401..b7b07f01 100644 --- a/crates/bashkit/tests/spec_cases/bash/blackbox-edge-cases.test.sh +++ b/crates/bashkit/tests/spec_cases/bash/blackbox-edge-cases.test.sh @@ -587,11 +587,10 @@ file3.txt ### end ### chained_string_operations -### bash_diff: variable expansion in ${x#$var} operand not supported yet -# Multiple string operations — bash: "[hello world ]", bashkit: "[ hello world ]" +# Multiple string operations — strip leading whitespace x=" hello world "; x="${x#"${x%%[![:space:]]*}"}"; echo "[$x]" ### expect -[ hello world ] +[hello world ] ### end ### multiline_if diff --git a/crates/bashkit/tests/spec_cases/bash/var-op-test.test.sh b/crates/bashkit/tests/spec_cases/bash/var-op-test.test.sh index 504eb5eb..de3deae2 100644 --- a/crates/bashkit/tests/spec_cases/bash/var-op-test.test.sh +++ b/crates/bashkit/tests/spec_cases/bash/var-op-test.test.sh @@ -248,3 +248,49 @@ empty= assoc=v assoc=plus ### end + +### vop_suffix_removal_ansi_c_newline +# ${var%$'\n'} should strip trailing newline (issue #847) +str=$'abc\n' +r1="${str%$'\n'}" +echo "$r1" +### expect +abc +### end + +### vop_suffix_removal_var_newline +# ${var%${nl}} should strip trailing newline via variable (issue #847) +nl=$'\n' +str=$'abc\n' +r2="${str%${nl}}" +echo "$r2" +### expect +abc +### end + +### vop_prefix_removal_ansi_c_newline +# ${var#$'\n'} should strip leading newline (issue #847) +str=$'\nabc' +r3="${str#$'\n'}" +echo "$r3" +### expect +abc +### end + +### vop_suffix_removal_long_ansi_c +# ${var%%$'\n'} should strip trailing newline (issue #847) +str=$'abc\n' +r4="${str%%$'\n'}" +echo "$r4" +### expect +abc +### end + +### vop_prefix_removal_long_ansi_c +# ${var##$'\n'} should strip leading newline (issue #847) +str=$'\nabc' +r5="${str##$'\n'}" +echo "$r5" +### expect +abc +### end