diff --git a/crates/bashkit/src/interpreter/mod.rs b/crates/bashkit/src/interpreter/mod.rs index 46a23fc4..a6b1baab 100644 --- a/crates/bashkit/src/interpreter/mod.rs +++ b/crates/bashkit/src/interpreter/mod.rs @@ -5288,9 +5288,45 @@ impl Interpreter { } } - // For other words, expand to a single field + // For other words, expand to a single field then apply IFS word splitting + // when the word is unquoted and contains a command substitution. + // Per POSIX, unquoted $() results undergo field splitting on IFS. let expanded = self.expand_word(word).await?; - Ok(vec![expanded]) + + let has_command_subst = !word.quoted + && word + .parts + .iter() + .any(|p| matches!(p, WordPart::CommandSubstitution(_))); + + if has_command_subst { + // Split on IFS characters (default: space, tab, newline). + // Consecutive IFS-whitespace characters are collapsed (no empty fields). + let ifs = self + .variables + .get("IFS") + .cloned() + .unwrap_or_else(|| " \t\n".to_string()); + + if ifs.is_empty() { + // Empty IFS: no splitting + return Ok(vec![expanded]); + } + + let fields: Vec = expanded + .split(|c: char| ifs.contains(c)) + .filter(|s| !s.is_empty()) + .map(|s| s.to_string()) + .collect(); + + if fields.is_empty() { + // All-whitespace expansion produces zero fields (elision) + return Ok(Vec::new()); + } + Ok(fields) + } else { + Ok(vec![expanded]) + } } /// Apply parameter expansion operator diff --git a/crates/bashkit/tests/spec_cases/bash/command-subst.test.sh b/crates/bashkit/tests/spec_cases/bash/command-subst.test.sh index 5d35cca3..38210a8d 100644 --- a/crates/bashkit/tests/spec_cases/bash/command-subst.test.sh +++ b/crates/bashkit/tests/spec_cases/bash/command-subst.test.sh @@ -152,3 +152,36 @@ echo "$(echo "hello\"world")" ### expect hello"world ### end + +### subst_word_split_for_loop +# Command substitution output is word-split in for-loop list context +count=0 +for f in $(printf '/src/one.txt\n/src/two.txt\n/src/three.txt\n'); do + count=$((count + 1)) +done +echo "$count" +### expect +3 +### end + +### subst_word_split_echo_multiword +# Command substitution producing space-separated words splits in for-loop +result="" +for w in $(echo "alpha beta gamma"); do + result="${result}[${w}]" +done +echo "$result" +### expect +[alpha][beta][gamma] +### end + +### subst_word_split_newlines +# Command substitution with newline-separated output splits on newlines +result="" +for line in $(printf 'x\ny\nz'); do + result="${result}(${line})" +done +echo "$result" +### expect +(x)(y)(z) +### end