Skip to content

Commit 183eaa2

Browse files
chaliyclaude
andauthored
fix(builtins): count newlines for wc -l instead of logical lines (#481)
## Summary - Fixed `wc -l` to count `\n` characters instead of using Rust's `.lines().count()`, matching real bash behavior - `printf "a\nb\nc"` now correctly returns 2 (two newlines) instead of 3 (three logical lines) - Added tests for wc -l in pipe contexts Closes #401 ## Test plan - [x] test_wc_l_in_pipe - [x] test_wc_l_in_pipe_subst - [x] test_wc_l_counts_newlines - [x] All existing wc tests pass Co-authored-by: Claude <noreply@anthropic.com>
1 parent fc51ce0 commit 183eaa2

File tree

2 files changed

+43
-1
lines changed

2 files changed

+43
-1
lines changed

crates/bashkit/src/builtins/wc.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -185,7 +185,8 @@ struct TextCounts {
185185

186186
/// Count lines, words, bytes, characters, and max line length in text
187187
fn count_text(text: &str) -> TextCounts {
188-
let lines = text.lines().count();
188+
// wc -l counts newline characters, not logical lines
189+
let lines = text.chars().filter(|&c| c == '\n').count();
189190
let words = text.split_whitespace().count();
190191
let bytes = text.len();
191192
let chars = text.chars().count();

crates/bashkit/src/interpreter/mod.rs

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9686,4 +9686,45 @@ bash /tmp/opts.sh -f xml -v
96869686
assert_eq!(lines[0], "FORMAT=json VERBOSE=1");
96879687
assert_eq!(lines[1], "FORMAT=xml VERBOSE=1");
96889688
}
9689+
9690+
#[tokio::test]
9691+
async fn test_wc_l_in_pipe() {
9692+
// Issue #401: wc -l in pipe returns -1 instead of actual count
9693+
let mut bash = crate::Bash::new();
9694+
let result = bash.exec(r#"echo -e "a\nb\nc" | wc -l"#).await.unwrap();
9695+
assert_eq!(result.exit_code, 0);
9696+
assert_eq!(result.stdout.trim(), "3");
9697+
}
9698+
9699+
#[tokio::test]
9700+
async fn test_wc_l_in_pipe_subst() {
9701+
// Issue #401: wc -l in command substitution with pipe
9702+
let mut bash = crate::Bash::new();
9703+
let result = bash
9704+
.exec(
9705+
r#"
9706+
cat > /tmp/data.csv << 'EOF'
9707+
name,score
9708+
alice,95
9709+
bob,87
9710+
carol,92
9711+
EOF
9712+
COUNT=$(tail -n +2 /tmp/data.csv | wc -l)
9713+
echo "count=$COUNT"
9714+
"#,
9715+
)
9716+
.await
9717+
.unwrap();
9718+
assert_eq!(result.exit_code, 0);
9719+
assert_eq!(result.stdout.trim(), "count=3");
9720+
}
9721+
9722+
#[tokio::test]
9723+
async fn test_wc_l_counts_newlines() {
9724+
// Issue #401: wc -l counts newline characters, not logical lines
9725+
let mut bash = crate::Bash::new();
9726+
// printf without trailing newline: 2 newlines = 2 lines per wc -l
9727+
let result = bash.exec(r#"printf "a\nb\nc" | wc -l"#).await.unwrap();
9728+
assert_eq!(result.stdout.trim(), "2");
9729+
}
96899730
}

0 commit comments

Comments
 (0)