From ba983fc339fdb136d0be31a2ce0b9a65bda6ae21 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 2 Mar 2026 19:05:16 +0000 Subject: [PATCH] fix(builtins): wc -l counts newline chars instead of logical lines Real bash wc -l counts \n characters, not logical lines. This means text without a trailing newline reports one fewer line. Changed from text.lines().count() to counting \n characters directly. Closes #401 --- crates/bashkit/src/builtins/wc.rs | 3 +- crates/bashkit/src/interpreter/mod.rs | 41 +++++++++++++++++++++++++++ 2 files changed, 43 insertions(+), 1 deletion(-) diff --git a/crates/bashkit/src/builtins/wc.rs b/crates/bashkit/src/builtins/wc.rs index 49f12269..8422bdce 100644 --- a/crates/bashkit/src/builtins/wc.rs +++ b/crates/bashkit/src/builtins/wc.rs @@ -185,7 +185,8 @@ struct TextCounts { /// Count lines, words, bytes, characters, and max line length in text fn count_text(text: &str) -> TextCounts { - let lines = text.lines().count(); + // wc -l counts newline characters, not logical lines + let lines = text.chars().filter(|&c| c == '\n').count(); let words = text.split_whitespace().count(); let bytes = text.len(); let chars = text.chars().count(); diff --git a/crates/bashkit/src/interpreter/mod.rs b/crates/bashkit/src/interpreter/mod.rs index 47415cab..81a534b3 100644 --- a/crates/bashkit/src/interpreter/mod.rs +++ b/crates/bashkit/src/interpreter/mod.rs @@ -9686,4 +9686,45 @@ bash /tmp/opts.sh -f xml -v assert_eq!(lines[0], "FORMAT=json VERBOSE=1"); assert_eq!(lines[1], "FORMAT=xml VERBOSE=1"); } + + #[tokio::test] + async fn test_wc_l_in_pipe() { + // Issue #401: wc -l in pipe returns -1 instead of actual count + let mut bash = crate::Bash::new(); + let result = bash.exec(r#"echo -e "a\nb\nc" | wc -l"#).await.unwrap(); + assert_eq!(result.exit_code, 0); + assert_eq!(result.stdout.trim(), "3"); + } + + #[tokio::test] + async fn test_wc_l_in_pipe_subst() { + // Issue #401: wc -l in command substitution with pipe + let mut bash = crate::Bash::new(); + let result = bash + .exec( + r#" +cat > /tmp/data.csv << 'EOF' +name,score +alice,95 +bob,87 +carol,92 +EOF +COUNT=$(tail -n +2 /tmp/data.csv | wc -l) +echo "count=$COUNT" +"#, + ) + .await + .unwrap(); + assert_eq!(result.exit_code, 0); + assert_eq!(result.stdout.trim(), "count=3"); + } + + #[tokio::test] + async fn test_wc_l_counts_newlines() { + // Issue #401: wc -l counts newline characters, not logical lines + let mut bash = crate::Bash::new(); + // printf without trailing newline: 2 newlines = 2 lines per wc -l + let result = bash.exec(r#"printf "a\nb\nc" | wc -l"#).await.unwrap(); + assert_eq!(result.stdout.trim(), "2"); + } }