From 1882ff5824347592a27d02794890aaea4c92d039 Mon Sep 17 00:00:00 2001 From: Daniel Hofstetter Date: Mon, 8 Dec 2025 11:29:03 +0100 Subject: [PATCH 1/2] nl: output number separator as bytes not as lossy string --- src/uu/nl/src/nl.rs | 24 ++++++++++++++++-------- tests/by-util/test_nl.rs | 13 +++++++------ 2 files changed, 23 insertions(+), 14 deletions(-) diff --git a/src/uu/nl/src/nl.rs b/src/uu/nl/src/nl.rs index 7d1f862aa5e..811b19d00b4 100644 --- a/src/uu/nl/src/nl.rs +++ b/src/uu/nl/src/nl.rs @@ -409,16 +409,24 @@ fn nl(reader: &mut BufReader, stats: &mut Stats, settings: &Settings translate!("nl-error-line-number-overflow"), )); }; - writeln!( - writer, - "{}{}{}", + let mut buf = Vec::with_capacity( + settings.number_width + settings.number_separator.len() + line.len() + 1, + ); + + buf.extend( settings .number_format - .format(line_number, settings.number_width), - settings.number_separator.to_string_lossy(), - String::from_utf8_lossy(&line), - ) - .map_err_context(|| translate!("nl-error-could-not-write"))?; + .format(line_number, settings.number_width) + .as_bytes(), + ); + buf.extend(settings.number_separator.as_encoded_bytes()); + buf.extend(String::from_utf8_lossy(&line).as_bytes()); + buf.push(b'\n'); + + writer + .write_all(&buf) + .map_err_context(|| translate!("nl-error-could-not-write"))?; + // update line number for the potential next line match line_number.checked_add(settings.line_increment) { Some(new_line_number) => stats.line_number = Some(new_line_number), diff --git a/tests/by-util/test_nl.rs b/tests/by-util/test_nl.rs index ab430b20bcc..b05f901cab9 100644 --- a/tests/by-util/test_nl.rs +++ b/tests/by-util/test_nl.rs @@ -209,23 +209,24 @@ fn test_number_separator() { #[test] #[cfg(target_os = "linux")] fn test_number_separator_non_utf8() { - use std::{ - ffi::{OsStr, OsString}, - os::unix::ffi::{OsStrExt, OsStringExt}, - }; + use std::{ffi::OsString, os::unix::ffi::OsStringExt}; let separator_bytes = [0xFF, 0xFE]; let mut v = b"--number-separator=".to_vec(); v.extend_from_slice(&separator_bytes); let arg = OsString::from_vec(v); - let separator = OsStr::from_bytes(&separator_bytes); + + let mut expected = Vec::with_capacity(14); + expected.extend(b" 1"); + expected.extend(separator_bytes); + expected.extend(b"test\n"); new_ucmd!() .arg(arg) .pipe_in("test") .succeeds() - .stdout_is(format!(" 1{}test\n", separator.to_string_lossy())); + .stdout_is_bytes(expected); } #[test] From db52eacf38111784a3c31e390f74f271ce5baf9e Mon Sep 17 00:00:00 2001 From: Daniel Hofstetter Date: Mon, 8 Dec 2025 15:32:32 +0100 Subject: [PATCH 2/2] nl: output content as bytes, not as string --- src/uu/nl/src/nl.rs | 2 +- tests/by-util/test_nl.rs | 12 ++++++++---- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/src/uu/nl/src/nl.rs b/src/uu/nl/src/nl.rs index 811b19d00b4..3bedcb3ac25 100644 --- a/src/uu/nl/src/nl.rs +++ b/src/uu/nl/src/nl.rs @@ -420,7 +420,7 @@ fn nl(reader: &mut BufReader, stats: &mut Stats, settings: &Settings .as_bytes(), ); buf.extend(settings.number_separator.as_encoded_bytes()); - buf.extend(String::from_utf8_lossy(&line).as_bytes()); + buf.extend(&line); buf.push(b'\n'); writer diff --git a/tests/by-util/test_nl.rs b/tests/by-util/test_nl.rs index b05f901cab9..0a56a11fdad 100644 --- a/tests/by-util/test_nl.rs +++ b/tests/by-util/test_nl.rs @@ -796,10 +796,14 @@ fn test_file_with_non_utf8_content() { at.write_bytes(filename, content); - ucmd.arg(filename).succeeds().stdout_is(format!( - " 1\ta\n 2\t{}\n 3\tb\n", - String::from_utf8_lossy(invalid_utf8) - )); + let mut expected = Vec::with_capacity(30); + expected.extend(b" 1\ta\n"); + expected.extend(b" 2\t"); + expected.extend(invalid_utf8); + expected.extend(b"\n"); + expected.extend(b" 3\tb\n"); + + ucmd.arg(filename).succeeds().stdout_is_bytes(expected); } // Regression tests for issue #9132: repeated flags should use last value