diff --git a/src/uu/stat/src/stat.rs b/src/uu/stat/src/stat.rs index 327e89a6888..7acb7b470cb 100644 --- a/src/uu/stat/src/stat.rs +++ b/src/uu/stat/src/stat.rs @@ -440,8 +440,19 @@ fn quote_file_name(file_name: &str, quoting_style: &QuotingStyle) -> String { format!("'{escaped}'") } QuotingStyle::ShellEscapeAlways => { - let quote = if file_name.contains('\'') { '"' } else { '\'' }; - format!("{quote}{file_name}{quote}") + if !file_name.contains(|x: char| x.is_control()) { + let quote = if file_name.contains('\'') { '"' } else { '\'' }; + return format!("{quote}{file_name}{quote}"); + } + let mut escaped_file_name = String::from("$'"); + for c in file_name.chars() { + match c { + '\n' => escaped_file_name.push_str("\\n"), + _ => escaped_file_name.push(c), + } + } + escaped_file_name.push('\''); + escaped_file_name } QuotingStyle::Quote => file_name.to_string(), } diff --git a/tests/by-util/test_stat.rs b/tests/by-util/test_stat.rs index 0aad7361bb8..1dfc442df1d 100644 --- a/tests/by-util/test_stat.rs +++ b/tests/by-util/test_stat.rs @@ -567,3 +567,25 @@ fn test_mount_point_combined_with_other_specifiers() { "Should print mount point, file name, and size" ); } + +#[test] +fn test_quote_file_name_with_newline() { + let ts = TestScenario::new(util_name!()); + let at = &ts.fixtures; + + let file_name = "nice\\n file"; + let formatted_file_name = &format!("$'{file_name}'"); + let expected_result = "{\"name\":\"\"$'nice\\n file'\"\"}\n"; + at.touch(formatted_file_name); + + let result = ts + .ucmd() + .args(&["-c", "{\"name\":\"%N\"}", formatted_file_name]) + .succeeds() + .stdout_move_str(); + + assert_eq!( + result, expected_result, + "We are testing that \\n is handled correctly" + ); +}