Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 8 additions & 4 deletions crates/bashkit/src/interpreter/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
34 changes: 32 additions & 2 deletions crates/bashkit/src/parser/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
46 changes: 46 additions & 0 deletions crates/bashkit/tests/spec_cases/bash/var-op-test.test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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
Loading