Skip to content

Commit 79bfe6b

Browse files
authored
fix(interpreter): apply word splitting for unquoted expansions in array assignments (#1025)
## Summary - `arr=($x)` now correctly word-splits unquoted variable expansions into multiple elements - Quoted words (e.g., `arr=("a b")`) are kept as single elements - Uses existing `expand_word_to_fields` for proper IFS-based splitting ## Why `x="hello world"; arr=($x)` produced a single-element array instead of splitting into two elements. This is a standard bash pattern for creating arrays from whitespace-separated strings. ## Tests - Added spec tests: `unquoted_expansion_word_split_in_array`, `unquoted_expansion_custom_ifs_in_array` - 100% bash comparison match (1701/1701) Closes #969
1 parent af74d2d commit 79bfe6b

File tree

2 files changed

+43
-17
lines changed

2 files changed

+43
-17
lines changed

crates/bashkit/src/interpreter/mod.rs

Lines changed: 25 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -3147,14 +3147,29 @@ impl Interpreter {
31473147
}
31483148
}
31493149
AssignmentValue::Array(words) => {
3150-
let mut expanded_values = Vec::new();
3150+
// Expand each word, applying IFS word splitting for unquoted
3151+
// variable expansions: x="a b"; arr=($x) → arr=([0]="a" [1]="b")
3152+
// Quoted words (e.g., '' or "foo") are kept as single elements.
3153+
let mut all_fields = Vec::new();
31513154
for word in words.iter() {
3152-
let has_command_subst = word
3153-
.parts
3154-
.iter()
3155-
.any(|p| matches!(p, WordPart::CommandSubstitution(_)));
3156-
let value = self.expand_word(word).await?;
3157-
expanded_values.push((value, has_command_subst));
3155+
let is_unquoted_expansion = !word.quoted
3156+
&& word.parts.iter().any(|p| {
3157+
matches!(
3158+
p,
3159+
WordPart::Variable(_)
3160+
| WordPart::CommandSubstitution(_)
3161+
| WordPart::ArithmeticExpansion(_)
3162+
| WordPart::ParameterExpansion { .. }
3163+
| WordPart::ArrayAccess { .. }
3164+
)
3165+
});
3166+
if is_unquoted_expansion {
3167+
let fields = self.expand_word_to_fields(word).await?;
3168+
all_fields.extend(fields);
3169+
} else {
3170+
let value = self.expand_word(word).await?;
3171+
all_fields.push(value);
3172+
}
31583173
}
31593174

31603175
// Resolve nameref for array assignments
@@ -3167,16 +3182,9 @@ impl Interpreter {
31673182
0
31683183
};
31693184

3170-
for (value, has_command_subst) in expanded_values {
3171-
if has_command_subst && !value.is_empty() {
3172-
for part in value.split_whitespace() {
3173-
arr.insert(idx, part.to_string());
3174-
idx += 1;
3175-
}
3176-
} else if !value.is_empty() || !has_command_subst {
3177-
arr.insert(idx, value);
3178-
idx += 1;
3179-
}
3185+
for field in all_fields {
3186+
arr.insert(idx, field);
3187+
idx += 1;
31803188
}
31813189
}
31823190
}

crates/bashkit/tests/spec_cases/bash/arrays.test.sh

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -235,3 +235,21 @@ echo "${arr[1]}"
235235
3
236236
y
237237
### end
238+
239+
### unquoted_expansion_word_split_in_array
240+
# arr=($x) should word-split on IFS
241+
x="alpha beta gamma"
242+
arr=($x)
243+
echo "${#arr[@]}"
244+
echo "${arr[1]}"
245+
### expect
246+
3
247+
beta
248+
### end
249+
250+
### unquoted_expansion_custom_ifs_in_array
251+
# arr=($x) with custom IFS
252+
IFS=","; x="a,b,c"; arr=($x); echo "${#arr[@]}"
253+
### expect
254+
3
255+
### end

0 commit comments

Comments
 (0)