From 8b4ed6d6f29653d2c542ab3403a32ac320ea01cd Mon Sep 17 00:00:00 2001 From: Diomidis Spinellis Date: Wed, 19 Aug 2026 10:41:03 +0300 Subject: [PATCH] Support F (output filename) command This also requires storing input_name as PathBuf to avoid lossy conversions when outputting an OS-specific byte sequence representing the file name. --- src/sed/command.rs | 4 +-- src/sed/compiler.rs | 4 +++ src/sed/error_handling.rs | 3 +- src/sed/mod.rs | 2 +- src/sed/processor.rs | 14 +++++++--- tests/by-util/test_sed.rs | 24 +++++++++++++++- ...\317\201\317\207\316\265\316\257\316\2771" | 14 ++++++++++ tests/fixtures/sed/output/filename_file | 14 ++++++++++ ...\317\201\317\207\316\265\316\257\316\2771" | 28 +++++++++++++++++++ 9 files changed, 98 insertions(+), 9 deletions(-) create mode 100644 "tests/fixtures/sed/input/\316\261\317\201\317\207\316\265\316\257\316\2771" create mode 100644 tests/fixtures/sed/output/filename_file create mode 100644 "tests/fixtures/sed/output/filename_\316\261\317\201\317\207\316\265\316\257\316\2771" diff --git a/src/sed/command.rs b/src/sed/command.rs index dbf3786e..30a47be1 100644 --- a/src/sed/command.rs +++ b/src/sed/command.rs @@ -40,8 +40,8 @@ pub struct ProcessingContext { pub null_data: bool, // Other context - /// Currently processed input file name (not script) in quoted form - pub input_name: String, + /// Currently processed input file name (not script) + pub input_name: PathBuf, /// Current input line number pub line_number: usize, /// True if this is the last address of a range diff --git a/src/sed/compiler.rs b/src/sed/compiler.rs index 0e7d5ca2..457fd3d8 100644 --- a/src/sed/compiler.rs +++ b/src/sed/compiler.rs @@ -1632,6 +1632,10 @@ fn get_cmd_spec( n_addr: 2, handler: compile_execute_command, }), + 'F' if !posix => Ok(CommandSpec { + n_addr: 2, + handler: compile_empty_command, + }), 'r' => Ok(CommandSpec { n_addr: if posix { 1 } else { 2 }, handler: compile_read_file_command, diff --git a/src/sed/error_handling.rs b/src/sed/error_handling.rs index 0ce3454c..8f2bb250 100644 --- a/src/sed/error_handling.rs +++ b/src/sed/error_handling.rs @@ -14,6 +14,7 @@ use crate::sed::script_line_provider::ScriptLineProvider; use std::rc::Rc; +use uucore::display::Quotable; use uucore::error::{UResult, USimpleError}; #[derive(Clone, Debug)] @@ -108,7 +109,7 @@ pub fn input_runtime_error( location.input_name, location.line_number, location.column_number, - context.input_name, + context.input_name.quote(), context.line_number, msg.to_string() ), diff --git a/src/sed/mod.rs b/src/sed/mod.rs index 07c40011..7e1b0b68 100644 --- a/src/sed/mod.rs +++ b/src/sed/mod.rs @@ -248,7 +248,7 @@ fn build_context(matches: &ArgMatches) -> UResult { character_mode: character_mode_for_locale(&locale)?, // Other context - input_name: "".to_string(), + input_name: PathBuf::from("-"), line_number: 0, last_address: false, last_line: false, diff --git a/src/sed/processor.rs b/src/sed/processor.rs index 67441ca2..6549b9f2 100644 --- a/src/sed/processor.rs +++ b/src/sed/processor.rs @@ -737,6 +737,12 @@ fn process_file( } _ => panic!("invalid 'e' command data"), }, + 'F' => { + // Output current input file name. + let mut bytes = context.input_name.as_os_str().as_encoded_bytes().to_vec(); + bytes.push(b'\n'); + output.write_bytes(&bytes)?; + } 'g' => { // Replace pattern with the contents of the hold space. pattern.set_to_bytes(context.hold.content.clone(), context.hold.has_newline); @@ -945,11 +951,11 @@ pub fn process_all_files( let mut in_place = InPlace::new(context.clone()); let last_file_index = files.len() - 1; - for (index, path) in files.iter().enumerate() { + for (index, path) in files.into_iter().enumerate() { context.last_file = index == last_file_index; - let mut reader = LineReader::open(path) + let mut reader = LineReader::open(&path) .map_err_context(|| format!("error opening input file {}", path.quote()))?; - let output = in_place.begin(path)?; + let output = in_place.begin(&path)?; if context.separate || index == 0 { context.line_number = 0; @@ -960,7 +966,7 @@ pub fn process_all_files( context.hold.has_newline = true; } - context.input_name = path.quote().to_string(); + context.input_name = path; process_file(commands.clone(), &mut reader, output, context)?; // Handle any N command remains. diff --git a/tests/by-util/test_sed.rs b/tests/by-util/test_sed.rs index 026c0597..01fbbd78 100644 --- a/tests/by-util/test_sed.rs +++ b/tests/by-util/test_sed.rs @@ -1745,7 +1745,7 @@ fn write_two_files() -> std::io::Result<()> { } //////////////////////////////////////////////////////////// -// =, l commands +// =, l, F commands check_output!(number_continuous, ["/l2_/=", LINES1, LINES2]); check_output!(number_separate, ["-s", "/l._8/=", LINES1, LINES2]); check_output!(number_range, ["-e", "10,12=", LINES1]); @@ -1754,6 +1754,28 @@ check_output!(number_range_out_of_bounds, ["-e", "47,60=", LINES1]); check_output!(list_ascii, ["-n", "l 60", "input/ascii"]); check_output!(list_empty, ["-n", "l 60", "input/empty"]); +check_output!(filename_file, ["-n", r"F", LINES1]); +// Non-ASCII filename +check_output!(filename_αρχείο1, [r"F", "input/αρχείο1"]); + +#[test] +fn filename_stdin() { + new_ucmd!() + .args(&["-n", "F"]) + .pipe_in("a\nb\n") + .succeeds() + .stdout_is("-\n-\n"); +} + +#[test] +fn filename_non_posix() { + new_ucmd!() + .args(&["--posix", "F"]) + .fails() + .code_is(1) + .stderr_contains("invalid command code"); +} + /// List Unicode input under an explicit UTF-8 locale. #[test] fn list_unicode() { diff --git "a/tests/fixtures/sed/input/\316\261\317\201\317\207\316\265\316\257\316\2771" "b/tests/fixtures/sed/input/\316\261\317\201\317\207\316\265\316\257\316\2771" new file mode 100644 index 00000000..3bcc601e --- /dev/null +++ "b/tests/fixtures/sed/input/\316\261\317\201\317\207\316\265\316\257\316\2771" @@ -0,0 +1,14 @@ +l1_1 +l1_2 +l1_3 +l1_4 +l1_5 +l1_6 +l1_7 +l1_8 +l1_9 +l1_10 +l1_11 +l1_12 +l1_13 +l1_14 diff --git a/tests/fixtures/sed/output/filename_file b/tests/fixtures/sed/output/filename_file new file mode 100644 index 00000000..d1cf05ee --- /dev/null +++ b/tests/fixtures/sed/output/filename_file @@ -0,0 +1,14 @@ +input/lines1 +input/lines1 +input/lines1 +input/lines1 +input/lines1 +input/lines1 +input/lines1 +input/lines1 +input/lines1 +input/lines1 +input/lines1 +input/lines1 +input/lines1 +input/lines1 diff --git "a/tests/fixtures/sed/output/filename_\316\261\317\201\317\207\316\265\316\257\316\2771" "b/tests/fixtures/sed/output/filename_\316\261\317\201\317\207\316\265\316\257\316\2771" new file mode 100644 index 00000000..3f379e40 --- /dev/null +++ "b/tests/fixtures/sed/output/filename_\316\261\317\201\317\207\316\265\316\257\316\2771" @@ -0,0 +1,28 @@ +input/αρχείο1 +l1_1 +input/αρχείο1 +l1_2 +input/αρχείο1 +l1_3 +input/αρχείο1 +l1_4 +input/αρχείο1 +l1_5 +input/αρχείο1 +l1_6 +input/αρχείο1 +l1_7 +input/αρχείο1 +l1_8 +input/αρχείο1 +l1_9 +input/αρχείο1 +l1_10 +input/αρχείο1 +l1_11 +input/αρχείο1 +l1_12 +input/αρχείο1 +l1_13 +input/αρχείο1 +l1_14