diff --git a/src/uu/stat/locales/en-US.ftl b/src/uu/stat/locales/en-US.ftl index f36d7ec6faf..958be0f63da 100644 --- a/src/uu/stat/locales/en-US.ftl +++ b/src/uu/stat/locales/en-US.ftl @@ -56,6 +56,7 @@ stat-after-help = Valid format sequences for files (without `--file-system`): ## Error messages stat-error-invalid-quoting-style = Invalid quoting style: {$style} +stat-warning-invalid-env-quoting-style = ignoring invalid value of environment variable QUOTING_STYLE: '{$style}' stat-error-missing-operand = missing operand Try 'stat --help' for more information. stat-error-invalid-directive = {$directive}: invalid directive diff --git a/src/uu/stat/locales/fr-FR.ftl b/src/uu/stat/locales/fr-FR.ftl index e41b3190e3e..a0545234432 100644 --- a/src/uu/stat/locales/fr-FR.ftl +++ b/src/uu/stat/locales/fr-FR.ftl @@ -92,6 +92,7 @@ stat-word-birth = Créé ## Messages d'erreur stat-error-invalid-quoting-style = Style de guillemets invalide : {$style} +stat-warning-invalid-env-quoting-style = valeur invalide de la variable d'environnement QUOTING_STYLE ignorée : '{$style}' stat-error-missing-operand = opérande manquant Essayez 'stat --help' pour plus d'informations. stat-error-invalid-directive = {$directive} : directive invalide diff --git a/src/uu/stat/src/stat.rs b/src/uu/stat/src/stat.rs index 27f4b7d1ce2..6c5afff2bc6 100644 --- a/src/uu/stat/src/stat.rs +++ b/src/uu/stat/src/stat.rs @@ -451,16 +451,32 @@ fn quote_file_name(file_name: &str, quoting_style: &QuotingStyle) -> String { } } +fn warn_invalid_quoting_style(style: &str) { + use std::sync::atomic::{AtomicBool, Ordering}; + static WARNED: AtomicBool = AtomicBool::new(false); + if !WARNED.swap(true, Ordering::Relaxed) { + show_error!( + "{}", + translate!("stat-warning-invalid-env-quoting-style", "style" => style.to_string()) + ); + } +} + fn get_quoted_file_name( display_name: &str, file: &OsString, file_type: FileType, from_user: bool, ) -> Result { - let quoting_style = env::var("QUOTING_STYLE") - .ok() - .and_then(|style| style.parse().ok()) - .unwrap_or_default(); + let quoting_style = match env::var("QUOTING_STYLE") { + Ok(style) => style.parse().unwrap_or_else(|_| { + // Match GNU coreutils 9.11: warn (once) when QUOTING_STYLE is set + // to a value we don't understand, then fall back to the default. + warn_invalid_quoting_style(&style); + QuotingStyle::default() + }), + Err(_) => QuotingStyle::default(), + }; if file_type.is_symlink() { let quoted_display_name = quote_file_name(display_name, "ing_style); diff --git a/tests/by-util/test_stat.rs b/tests/by-util/test_stat.rs index 8347d49c795..c536a7744f7 100644 --- a/tests/by-util/test_stat.rs +++ b/tests/by-util/test_stat.rs @@ -3,6 +3,8 @@ // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. +// spell-checker:ignore crème brûlée + use uutests::at_and_ucmd; use uutests::new_ucmd; use uutests::unwrap_or_return; @@ -447,6 +449,52 @@ fn test_quoting_style_locale() { .stdout_only("\'\"\'\n"); } +#[test] +fn test_quoting_style_invalid_env() { + let ts = TestScenario::new(util_name!()); + let at = &ts.fixtures; + at.touch("baguette"); + at.touch("Croissant"); + at.touch("Escargot"); + + let needle = "ignoring invalid value of environment variable QUOTING_STYLE"; + + // A bogus value triggers exactly one warning across multiple files and the + // output falls back to the default (shell-escape) style. + let res = ts + .ucmd() + .env("QUOTING_STYLE", "fromage") + .args(&["-c", "nom=[%N]", "baguette", "Croissant", "Escargot"]) + .succeeds(); + res.stdout_is("nom=['baguette']\nnom=['Croissant']\nnom=['Escargot']\n"); + assert_eq!(res.stderr_str().matches(needle).count(), 1); + + // An empty value is also invalid and must be reported with empty quotes. + ts.ucmd() + .env("QUOTING_STYLE", "") + .args(&["-c", "%N", "baguette"]) + .succeeds() + .stdout_is("'baguette'\n") + .stderr_is("stat: ignoring invalid value of environment variable QUOTING_STYLE: ''\n"); + + // %%%N: a literal '%' followed by the quoted name, fallback style applies. + ts.ucmd() + .env("QUOTING_STYLE", "soufflé") + .args(&["-c", "%%%N", "baguette"]) + .succeeds() + .stdout_is("%'baguette'\n") + .stderr_is( + "stat: ignoring invalid value of environment variable QUOTING_STYLE: 'soufflé'\n", + ); + + // When the format never consults %N, QUOTING_STYLE must not be parsed at all. + ts.ucmd() + .env("QUOTING_STYLE", "crème-brûlée") + .args(&["-c", "taille=%s genre:%F brut=%n", "baguette"]) + .succeeds() + .no_stderr(); +} + #[test] fn test_printf_octal_1() { let ts = TestScenario::new(util_name!());