From a7b15320af897b58c6d393c8604aa112ad6aa8c2 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Sat, 30 May 2026 18:06:14 +0200 Subject: [PATCH] grep: don't emit the -T alignment tab on empty lines With -T, grep pads the prefix with a tab so line content lands on a tab stop. GNU omits that tab when the line has no content: an empty line prints just its prefix (a whitespace-only line still gets the tab). uu_grep always wrote the tab, so empty matched lines gained a spurious trailing tab. Gate the tab on non-empty content. Fixes the GNU testsuite 'initial-tab' test. --- src/output.rs | 6 ++++++ tests/test_grep.rs | 18 ++++++++++++++++++ 2 files changed, 24 insertions(+) diff --git a/src/output.rs b/src/output.rs index 30cdc5f..21343ab 100644 --- a/src/output.rs +++ b/src/output.rs @@ -71,6 +71,7 @@ impl<'a> OutputWriter<'a> { view.line_number, view.byte_offset + start as u64, b':', + false, )?; self.write_colored_bytes( @@ -90,6 +91,7 @@ impl<'a> OutputWriter<'a> { view.line_number, view.byte_offset, if view.is_match { b':' } else { b'-' }, + view.line.is_empty(), )?; let mut last_end = 0; @@ -125,6 +127,7 @@ impl<'a> OutputWriter<'a> { line_number: u64, byte_offset: u64, sep_char: u8, + content_empty: bool, ) -> io::Result<()> { if self.config.show_filename { self.write_colored_fmt( @@ -155,7 +158,10 @@ impl<'a> OutputWriter<'a> { self.write_separator(sep_char)?; } + // GNU grep aligns content with a tab under -T, but only when there is + // content to align: an empty line keeps just its prefix (no tab). if self.config.initial_tab + && !content_empty && (self.config.line_number || self.config.byte_offset || self.config.show_filename) { self.out.write_all(b"\t")?; diff --git a/tests/test_grep.rs b/tests/test_grep.rs index 5e487ea..b1648e6 100644 --- a/tests/test_grep.rs +++ b/tests/test_grep.rs @@ -126,6 +126,24 @@ fn ere_invalid_pattern_is_error() { .stderr_contains("invalid pattern"); } +#[test] +fn initial_tab_skips_empty_lines() { + // -T aligns content with a tab, but GNU omits the tab for an empty line + // (a whitespace-only line still gets one). -H forces the filename prefix + // on, so the tab is exercised. + let (s, mut c) = ucmd(); + s.fixtures.write("in", "x\n\n"); + c.args(&["-T", "-H", "^", "in"]) + .succeeds() + .stdout_is("in:\tx\nin:\n"); + + let (s, mut c) = ucmd(); + s.fixtures.write("in", "x\n \n"); + c.args(&["-T", "-H", "^", "in"]) + .succeeds() + .stdout_is("in:\tx\nin:\t \n"); +} + #[test] fn fixed_string_is_literal() { // Metacharacters are not interpreted.