From 446b0d14e0b9627c4eb970f78eca535ac046187c Mon Sep 17 00:00:00 2001 From: can1357 Date: Thu, 19 Mar 2026 00:41:46 +0100 Subject: [PATCH 1/4] env: reject non-GNU variable expansion --- src/uu/env/locales/en-US.ftl | 1 + src/uu/env/locales/fr-FR.ftl | 1 + src/uu/env/src/env.rs | 31 +++++---- src/uu/env/src/split_iterator.rs | 13 +--- src/uu/env/src/variable_parser.rs | 104 ++++++------------------------ tests/by-util/test_env.rs | 36 +++++------ 6 files changed, 60 insertions(+), 126 deletions(-) diff --git a/src/uu/env/locales/en-US.ftl b/src/uu/env/locales/en-US.ftl index a460c69fba4..d3d10b25512 100644 --- a/src/uu/env/locales/en-US.ftl +++ b/src/uu/env/locales/en-US.ftl @@ -23,6 +23,7 @@ env-error-backslash-c-not-allowed = '\c' must not appear in double-quoted -S str env-error-invalid-sequence = invalid sequence '\{ $char }' in -S at position { $position } env-error-missing-closing-brace = Missing closing brace at position { $position } env-error-missing-variable = Missing variable name at position { $position } +env-error-only-braced-variable = only ${VARNAME} expansion is supported at position { $position } env-error-missing-closing-brace-after-value = Missing closing brace after default value at position { $position } env-error-unexpected-number = Unexpected character: '{ $char }', expected variable name must not start with 0..9 at position { $position } env-error-expected-brace-or-colon = Unexpected character: '{ $char }', expected a closing brace ('{"}"}') or colon (':') at position { $position } diff --git a/src/uu/env/locales/fr-FR.ftl b/src/uu/env/locales/fr-FR.ftl index 2ca1968d230..ac54dcd7b20 100644 --- a/src/uu/env/locales/fr-FR.ftl +++ b/src/uu/env/locales/fr-FR.ftl @@ -23,6 +23,7 @@ env-error-backslash-c-not-allowed = '\\c' ne doit pas apparaître dans une chaî env-error-invalid-sequence = séquence invalide '\\{ $char }' dans -S à la position { $position } env-error-missing-closing-brace = Accolade fermante manquante à la position { $position } env-error-missing-variable = Nom de variable manquant à la position { $position } +env-error-only-braced-variable = seule l'expansion ${VARNAME} est prise en charge à la position { $position } env-error-missing-closing-brace-after-value = Accolade fermante manquante après la valeur par défaut à la position { $position } env-error-unexpected-number = Caractère inattendu : '{ $char }', le nom de variable attendu ne doit pas commencer par 0..9 à la position { $position } env-error-expected-brace-or-colon = Caractère inattendu : '{ $char }', accolade fermante ('{"\\}"}') ou deux-points (':') attendu à la position { $position } diff --git a/src/uu/env/src/env.rs b/src/uu/env/src/env.rs index 7b36c7c6355..78bc49ea45b 100644 --- a/src/uu/env/src/env.rs +++ b/src/uu/env/src/env.rs @@ -63,12 +63,10 @@ pub enum EnvError { EnvParsingOfVariableMissingClosingBrace(usize), #[error("{}", translate!("env-error-missing-variable", "position" => .0))] EnvParsingOfMissingVariable(usize), - #[error("{}", translate!("env-error-missing-closing-brace-after-value", "position" => .0))] - EnvParsingOfVariableMissingClosingBraceAfterValue(usize), + #[error("{}", translate!("env-error-only-braced-variable", "position" => .0))] + EnvParsingOfVariableOnlyBracedName(usize), #[error("{}", translate!("env-error-unexpected-number", "position" => .0, "char" => .1.clone()))] EnvParsingOfVariableUnexpectedNumber(usize, String), - #[error("{}", translate!("env-error-expected-brace-or-colon", "position" => .0, "char" => .1.clone()))] - EnvParsingOfVariableExceptedBraceOrColon(usize, String), #[error("")] EnvReachedEnd, #[error("")] @@ -481,7 +479,7 @@ pub fn parse_args_from_str(text: &NativeIntStr) -> UResult> 125, translate!("env-error-variable-name-issue", "position" => pos, "error" => e), ), - EnvError::EnvParsingOfVariableMissingClosingBraceAfterValue(pos) => USimpleError::new( + EnvError::EnvParsingOfVariableOnlyBracedName(pos) => USimpleError::new( 125, translate!("env-error-variable-name-issue", "position" => pos, "error" => e), ), @@ -489,10 +487,6 @@ pub fn parse_args_from_str(text: &NativeIntStr) -> UResult> 125, translate!("env-error-variable-name-issue", "position" => pos, "error" => e), ), - EnvError::EnvParsingOfVariableExceptedBraceOrColon(pos, _) => USimpleError::new( - 125, - translate!("env-error-variable-name-issue", "position" => pos, "error" => e), - ), _ => USimpleError::new( 125, translate!("env-error-generic", "error" => format!("{e:?}")), @@ -1246,21 +1240,34 @@ mod tests { .contains("variable name issue (at 10): Missing closing brace") ); - let result = parse_args_from_str(&NCvt::convert(r"echo ${FOO:-value")); + let result = parse_args_from_str(&NCvt::convert(r"echo ${FOO:-value}")); assert!(result.is_err()); assert!( result .unwrap_err() .to_string() - .contains("variable name issue (at 17): Missing closing brace after default value") + .contains("only ${VARNAME} expansion is supported") ); + let result = parse_args_from_str(&NCvt::convert(r"echo $FOO")); + assert!(result.is_err()); + assert!( + result + .unwrap_err() + .to_string() + .contains("only ${VARNAME} expansion is supported") + ); let result = parse_args_from_str(&NCvt::convert(r"echo ${1FOO}")); assert!(result.is_err()); assert!(result.unwrap_err().to_string().contains("variable name issue (at 7): Unexpected character: '1', expected variable name must not start with 0..9")); let result = parse_args_from_str(&NCvt::convert(r"echo ${FOO?}")); assert!(result.is_err()); - assert!(result.unwrap_err().to_string().contains("variable name issue (at 10): Unexpected character: '?', expected a closing brace ('}') or colon (':')")); + assert!( + result + .unwrap_err() + .to_string() + .contains("only ${VARNAME} expansion is supported") + ); } } diff --git a/src/uu/env/src/split_iterator.rs b/src/uu/env/src/split_iterator.rs index a511fb9e037..e778ca1cc95 100644 --- a/src/uu/env/src/split_iterator.rs +++ b/src/uu/env/src/split_iterator.rs @@ -99,18 +99,11 @@ impl<'a> SplitIterator<'a> { parser: self.get_parser_mut(), }; - let (name, default) = var_parse.parse_variable()?; + let name = var_parse.parse_variable()?; let varname_os_str_cow = from_native_int_representation(Cow::Borrowed(name)); - let value = std::env::var_os(varname_os_str_cow); - match (&value, default) { - (None, None) => {} // do nothing, just replace it with "" - (Some(value), _) => { - self.expander.put_string(value); - } - (None, Some(default)) => { - self.expander.put_native_string(default); - } + if let Some(value) = std::env::var_os(varname_os_str_cow) { + self.expander.put_string(value); } Ok(()) diff --git a/src/uu/env/src/variable_parser.rs b/src/uu/env/src/variable_parser.rs index da200c9dba6..032a801604c 100644 --- a/src/uu/env/src/variable_parser.rs +++ b/src/uu/env/src/variable_parser.rs @@ -34,126 +34,60 @@ impl<'a> VariableParser<'a, '_> { Ok(()) } - fn parse_braced_variable_name( - &mut self, - ) -> Result<(&'a NativeIntStr, Option<&'a NativeIntStr>), EnvError> { + fn parse_braced_variable_name(&mut self) -> Result<&'a NativeIntStr, EnvError> { let pos_start = self.parser.get_peek_position(); self.check_variable_name_start()?; - let (varname_end, default_end); - loop { + let varname_end = loop { match self.get_current_char() { None => { return Err(EnvError::EnvParsingOfVariableMissingClosingBrace( self.parser.get_peek_position(), )); } - Some(c) if !c.is_ascii() || c.is_ascii_alphanumeric() || c == '_' => { + Some(c) if c.is_ascii_alphanumeric() || c == '_' => { self.skip_one()?; } - Some(':') => { - varname_end = self.parser.get_peek_position(); - loop { - match self.get_current_char() { - None => { - return Err( - EnvError::EnvParsingOfVariableMissingClosingBraceAfterValue( - self.parser.get_peek_position(), - ), - ); - } - Some('}') => { - default_end = Some(self.parser.get_peek_position()); - self.skip_one()?; - break; - } - Some(_) => { - self.skip_one()?; - } - } - } - break; - } Some('}') => { - varname_end = self.parser.get_peek_position(); - default_end = None; + let varname_end = self.parser.get_peek_position(); self.skip_one()?; - break; + break varname_end; } - Some(c) => { - return Err(EnvError::EnvParsingOfVariableExceptedBraceOrColon( + Some(_) => { + return Err(EnvError::EnvParsingOfVariableOnlyBracedName( self.parser.get_peek_position(), - c.to_string(), )); } } - } - - let default_opt = if let Some(default_end) = default_end { - Some(self.parser.substring(&Range { - start: varname_end + 1, - end: default_end, - })) - } else { - None }; - let varname = self.parser.substring(&Range { - start: pos_start, - end: varname_end, - }); - - Ok((varname, default_opt)) - } - - fn parse_unbraced_variable_name(&mut self) -> Result<&'a NativeIntStr, EnvError> { - let pos_start = self.parser.get_peek_position(); - - self.check_variable_name_start()?; - - loop { - match self.get_current_char() { - None => break, - Some(c) if c.is_ascii_alphanumeric() || c == '_' => { - self.skip_one()?; - } - Some(_) => break, - } - } - - let pos_end = self.parser.get_peek_position(); - - if pos_end == pos_start { + if varname_end == pos_start { return Err(EnvError::EnvParsingOfMissingVariable(pos_start)); } let varname = self.parser.substring(&Range { start: pos_start, - end: pos_end, + end: varname_end, }); Ok(varname) } - pub fn parse_variable( - &mut self, - ) -> Result<(&'a NativeIntStr, Option<&'a NativeIntStr>), EnvError> { + pub fn parse_variable(&mut self) -> Result<&'a NativeIntStr, EnvError> { self.skip_one()?; - let (name, default) = match self.get_current_char() { - None => { - return Err(EnvError::EnvParsingOfMissingVariable( - self.parser.get_peek_position(), - )); - } + match self.get_current_char() { + None => Err(EnvError::EnvParsingOfMissingVariable( + self.parser.get_peek_position(), + )), Some('{') => { self.skip_one()?; - self.parse_braced_variable_name()? + self.parse_braced_variable_name() } - Some(_) => (self.parse_unbraced_variable_name()?, None), - }; - - Ok((name, default)) + Some(_) => Err(EnvError::EnvParsingOfVariableOnlyBracedName( + self.parser.get_peek_position(), + )), + } } } diff --git a/tests/by-util/test_env.rs b/tests/by-util/test_env.rs index 4a989bd053b..64de3eeff84 100644 --- a/tests/by-util/test_env.rs +++ b/tests/by-util/test_env.rs @@ -1860,21 +1860,19 @@ fn test_shebang_error() { #[test] #[cfg(not(target_os = "windows"))] -fn test_braced_variable_with_default_value() { - new_ucmd!() - .arg("-Secho ${UNSET_VAR_UNLIKELY_12345:fallback}") - .succeeds() - .stdout_is("fallback\n"); -} - -#[test] -#[cfg(not(target_os = "windows"))] -fn test_braced_variable_with_default_when_set() { - new_ucmd!() - .env("TEST_VAR_12345", "actual") - .arg("-Secho ${TEST_VAR_12345:fallback}") - .succeeds() - .stdout_is("actual\n"); +fn test_reject_shell_style_variable_expansions() { + for split in [ + "-Secho $TEST_VAR_12345", + "-Secho ${TEST_VAR_12345:fallback}", + "-Secho ${TEST_VAR_12345:-fallback}", + "-Secho ${TEST_VAR_12345-default}", + ] { + new_ucmd!() + .env("TEST_VAR_12345", "value") + .arg(split) + .fails_with_code(125) + .stderr_contains("only ${VARNAME} expansion is supported"); + } } #[test] @@ -1896,11 +1894,11 @@ fn test_braced_variable_error_missing_closing_brace() { } #[test] -fn test_braced_variable_error_missing_closing_brace_after_default() { +fn test_braced_variable_error_rejects_default_syntax() { new_ucmd!() - .arg("-Secho ${FOO:-value") + .arg("-Secho ${FOO:-value}") .fails_with_code(125) - .stderr_contains("Missing closing brace after default value"); + .stderr_contains("only ${VARNAME} expansion is supported"); } #[test] @@ -1916,7 +1914,7 @@ fn test_braced_variable_error_unexpected_character() { new_ucmd!() .arg("-Secho ${FOO?}") .fails_with_code(125) - .stderr_contains("Unexpected character: '?'"); + .stderr_contains("only ${VARNAME} expansion is supported"); } #[test] From 27d60871ed83cd657bcbff508f7ab388f5a83a7e Mon Sep 17 00:00:00 2001 From: can1357 Date: Thu, 19 Mar 2026 00:42:16 +0100 Subject: [PATCH 2/4] env: defer chdir until after env setup --- src/uu/env/src/env.rs | 3 +-- tests/by-util/test_env.rs | 30 ++++++++++++++++++++++++++++++ 2 files changed, 31 insertions(+), 2 deletions(-) diff --git a/src/uu/env/src/env.rs b/src/uu/env/src/env.rs index 78bc49ea45b..6f9ea628286 100644 --- a/src/uu/env/src/env.rs +++ b/src/uu/env/src/env.rs @@ -708,8 +708,6 @@ impl EnvAppData { &signal_apply_all, )?; - apply_change_directory(&opts)?; - // NOTE: we manually set and unset the env vars below rather than using Command::env() to more // easily handle the case where no command is given @@ -748,6 +746,7 @@ impl EnvAppData { } } + apply_change_directory(&opts)?; if opts.program.is_empty() { // no program provided, so just dump all env vars to stdout print_all_env_vars(opts.line_ending)?; diff --git a/tests/by-util/test_env.rs b/tests/by-util/test_env.rs index 64de3eeff84..1db86c70def 100644 --- a/tests/by-util/test_env.rs +++ b/tests/by-util/test_env.rs @@ -480,6 +480,36 @@ fn test_fail_change_directory() { assert!(out.contains("env: cannot change directory to ")); } +#[test] +fn test_chdir_happens_after_relative_file_loading() { + let scene = TestScenario::new(util_name!()); + scene.fixtures.mkdir("target"); + scene + .fixtures + .write("config.env", "CONFIG_SOURCE=from-root\n"); + scene + .fixtures + .write("target/config.env", "CONFIG_SOURCE=from-target\n"); + + let out = scene + .ucmd() + .args(&["--chdir", "target", "--file", "config.env", "-i"]) + .arg(uutests::util::get_tests_binary()) + .arg(util_name!()) + .succeeds() + .stdout_move_str(); + + assert!( + out.contains("CONFIG_SOURCE=from-root\n"), + "expected config file from invocation directory, got: {out:?}" + ); + assert!( + !out.contains("CONFIG_SOURCE=from-target\n"), + "unexpectedly loaded config from --chdir target directory: {out:?}" + ); +} + + #[cfg(not(target_os = "windows"))] // windows has no executable "echo", its only supported as part of a batch-file #[test] fn test_split_string_into_args_one_argument_no_quotes() { From 02f81e7c2bf11524a9b3ee9385e3227341331d1f Mon Sep 17 00:00:00 2001 From: can1357 Date: Thu, 19 Mar 2026 00:43:09 +0100 Subject: [PATCH 3/4] env: fix split-string option form parsing --- src/uu/env/src/env.rs | 62 +++++++++++++++++++++++++++++++++++++-- tests/by-util/test_env.rs | 25 ++++++++++++++++ 2 files changed, 84 insertions(+), 3 deletions(-) diff --git a/src/uu/env/src/env.rs b/src/uu/env/src/env.rs index 6f9ea628286..90391c1c6b1 100644 --- a/src/uu/env/src/env.rs +++ b/src/uu/env/src/env.rs @@ -507,13 +507,29 @@ fn check_and_handle_string_args( prefix_to_test: &str, all_args: &mut Vec, do_debug_print_args: Option<&Vec>, + require_non_empty_payload: bool, + strip_optional_leading_equals: bool, ) -> UResult { let native_arg = NCvt::convert(arg); if let Some(remaining_arg) = native_arg.strip_prefix(&*NCvt::convert(prefix_to_test)) { + if require_non_empty_payload && remaining_arg.is_empty() { + return Ok(false); + } + if let Some(input_args) = do_debug_print_args { debug_print_args(input_args); // do it here, such that its also printed when we get an error/panic during parsing } + let remaining_arg = if strip_optional_leading_equals { + if let Some(stripped_remaining_arg) = remaining_arg.strip_prefix(&*NCvt::convert("=")) { + stripped_remaining_arg + } else { + remaining_arg + } + } else { + remaining_arg + }; + let arg_strings = parse_args_from_str(remaining_arg)?; all_args.extend( arg_strings @@ -568,7 +584,12 @@ impl EnvAppData { options::UNSET, ]; let short_flags_with_args = ['a', 'C', 'f', 'u']; + let mut consumed_split_payload_arg: Option = None; for (n, arg) in original_args.iter().enumerate() { + if consumed_split_payload_arg == Some(n) { + consumed_split_payload_arg = None; + continue; + } let arg_str = arg.to_string_lossy(); // Stop processing env flags once we reach the command or -- argument if 0 < n @@ -583,13 +604,21 @@ impl EnvAppData { } expecting_arg = false; match arg { - b if check_and_handle_string_args(b, "--split-string", &mut all_args, None)? => { + b if check_and_handle_string_args( + b, + "--split-string", + &mut all_args, + None, + true, + true, + )? => + { self.had_string_argument = true; } - b if check_and_handle_string_args(b, "-S", &mut all_args, None)? => { + b if check_and_handle_string_args(b, "-S", &mut all_args, None, true, false)? => { self.had_string_argument = true; } - b if check_and_handle_string_args(b, "-vS", &mut all_args, None)? => { + b if check_and_handle_string_args(b, "-vS", &mut all_args, None, true, false)? => { self.do_debug_printing = true; self.had_string_argument = true; } @@ -598,12 +627,39 @@ impl EnvAppData { "-vvS", &mut all_args, Some(original_args), + true, + false, )? => { self.do_debug_printing = true; self.do_input_debug_printing = Some(false); // already done self.had_string_argument = true; } + b if b == "--split-string" || b == "-S" || b == "-vS" || b == "-vvS" => { + let Some(next_arg) = original_args.get(n + 1) else { + all_args.push(arg.clone()); + continue; + }; + + if b == "-vS" || b == "-vvS" { + self.do_debug_printing = true; + } + if b == "-vvS" { + debug_print_args(original_args); + self.do_input_debug_printing = Some(false); + } + + let native_next_arg = NCvt::convert(next_arg); + let arg_strings = parse_args_from_str(native_next_arg.as_ref())?; + all_args.extend( + arg_strings + .into_iter() + .map(from_native_int_representation_owned), + ); + self.had_string_argument = true; + expecting_arg = false; + consumed_split_payload_arg = Some(n + 1); + } _ => { if let Some(flag) = arg_str.strip_prefix("--") { if flags_with_args.contains(&flag) { diff --git a/tests/by-util/test_env.rs b/tests/by-util/test_env.rs index 1db86c70def..a69640113e5 100644 --- a/tests/by-util/test_env.rs +++ b/tests/by-util/test_env.rs @@ -586,6 +586,31 @@ fn test_split_string_into_args_long_option_whitespace_handling() { assert_eq!(out, "xAx\nxBx\n"); } +#[cfg(not(target_os = "windows"))] // no printf available +#[test] +fn test_split_string_option_forms_match_gnu_required_argument_handling() { + let scene = TestScenario::new(util_name!()); + + scene + .ucmd() + .args(&["-S", "printf x:%s\\n one two"]) + .succeeds() + .stdout_is("x:one\nx:two\n"); + + scene + .ucmd() + .args(&["--split-string", "printf x:%s\\n one two"]) + .succeeds() + .stdout_is("x:one\nx:two\n"); + + scene + .ucmd() + .arg("--split-string=printf x:%s\\n one two") + .succeeds() + .stdout_is("x:one\nx:two\n"); +} + + #[cfg(not(target_os = "windows"))] // no printf available #[test] fn test_split_string_into_args_debug_output_whitespace_handling() { From ecc28f77f85286fc97da3380d75c2a8376fa7d88 Mon Sep 17 00:00:00 2001 From: can1357 Date: Thu, 19 Mar 2026 00:44:16 +0100 Subject: [PATCH 4/4] env: keep backslashes literal in single quotes --- src/uu/env/src/split_iterator.rs | 7 +-- tests/by-util/test_env.rs | 89 +++++++++++++++++++++++--------- 2 files changed, 65 insertions(+), 31 deletions(-) diff --git a/src/uu/env/src/split_iterator.rs b/src/uu/env/src/split_iterator.rs index e778ca1cc95..692f4601630 100644 --- a/src/uu/env/src/split_iterator.rs +++ b/src/uu/env/src/split_iterator.rs @@ -279,15 +279,10 @@ impl<'a> SplitIterator<'a> { self.take_one()?; Ok(()) } - Some(c) if REPLACEMENTS.iter().any(|&x| x.0 == c) => { - // See GNU test-suite e11: In single quotes, \t remains as it is. - // Comparing with GNU behavior: \a is not accepted and issues an error. - // So apparently only known sequences are allowed, even though they are not expanded.... bug of GNU? + Some(_) => { self.push_char_to_word(BACKSLASH); - self.take_one()?; Ok(()) } - Some(c) => Err(self.make_invalid_sequence_backslash_xin_minus_s(c)), } } diff --git a/tests/by-util/test_env.rs b/tests/by-util/test_env.rs index a69640113e5..4962618e550 100644 --- a/tests/by-util/test_env.rs +++ b/tests/by-util/test_env.rs @@ -509,7 +509,6 @@ fn test_chdir_happens_after_relative_file_loading() { ); } - #[cfg(not(target_os = "windows"))] // windows has no executable "echo", its only supported as part of a batch-file #[test] fn test_split_string_into_args_one_argument_no_quotes() { @@ -610,7 +609,6 @@ fn test_split_string_option_forms_match_gnu_required_argument_handling() { .stdout_is("x:one\nx:two\n"); } - #[cfg(not(target_os = "windows"))] // no printf available #[test] fn test_split_string_into_args_debug_output_whitespace_handling() { @@ -658,6 +656,67 @@ fn test_gnu_e20() { assert_eq!(out.stdout_str(), output); } +#[cfg(not(target_os = "windows"))] // no printf available +#[test] +fn test_split_string_single_quotes_keep_unknown_backslash_sequences_literal() { + let scene = TestScenario::new(util_name!()); + scene + .ucmd() + .arg("-Sprintf %s '\\x'") + .succeeds() + .stdout_is("\\x"); + scene + .ucmd() + .arg("-Sprintf %s '\\a'") + .succeeds() + .stdout_is("\\a"); + scene + .ucmd() + .arg("-Sprintf %s '\\`'") + .succeeds() + .stdout_is("\\`"); + scene + .ucmd() + .arg("-Sprintf %s '\\q'") + .succeeds() + .stdout_is("\\q"); + scene + .ucmd() + .arg("-Sprintf %s '\\|'") + .succeeds() + .stdout_is("\\|"); + scene + .ucmd() + .arg("-Sprintf %s '\\9'") + .succeeds() + .stdout_is("\\9"); +} + +#[cfg(not(target_os = "windows"))] // no printf available +#[test] +fn test_split_string_backslash_a_behavior_matches_gnu_quoting_context() { + let scene = TestScenario::new(util_name!()); + + scene + .ucmd() + .arg("-Sprintf %s '\\a'") + .succeeds() + .stdout_is("\\a"); + + scene + .ucmd() + .arg("-Sprintf %s \"\\a\"") + .fails_with_code(125) + .no_stdout() + .stderr_contains("invalid sequence '\\a' in -S"); + + scene + .ucmd() + .arg("-Sprintf %s \\a") + .fails_with_code(125) + .no_stdout() + .stderr_contains("invalid sequence '\\a' in -S"); +} #[test] #[allow(clippy::cognitive_complexity)] // Ignore clippy lint of too long function sign fn test_env_parsing_errors() { @@ -687,12 +746,6 @@ fn test_env_parsing_errors() { .no_stdout() .stderr_is("env: invalid sequence '\\a' in -S at position 2\n"); - ts.ucmd() - .arg("-S'\\a'") // single quotes, invalid escape sequence a - .fails_with_code(125) - .no_stdout() - .stderr_is("env: invalid sequence '\\a' in -S at position 2\n"); - ts.ucmd() .arg(r"-S\|\&\;") // no quotes, invalid escape sequence | .fails_with_code(125) @@ -723,12 +776,6 @@ fn test_env_parsing_errors() { .no_stdout() .stderr_is("env: invalid sequence '\\`' in -S at position 2\n"); - ts.ucmd() - .arg(r"-S'\`\&\;'") // single quotes, invalid escape sequence ` - .fails_with_code(125) - .no_stdout() - .stderr_is("env: invalid sequence '\\`' in -S at position 2\n"); - ts.ucmd() .arg(r"-S\`") // ` escaped without quotes .fails_with_code(125) @@ -741,12 +788,6 @@ fn test_env_parsing_errors() { .no_stdout() .stderr_is("env: invalid sequence '\\`' in -S at position 2\n"); - ts.ucmd() - .arg(r"-S'\`'") // ` escaped in single quotes - .fails_with_code(125) - .no_stdout() - .stderr_is("env: invalid sequence '\\`' in -S at position 2\n"); - ts.ucmd() .args(&[r"-S\🦉"]) // ` escaped in single quotes .fails_with_code(125) @@ -1334,7 +1375,7 @@ mod tests_split_iterator { assert_eq!(split("'\\"), Err(EnvError::EnvMissingClosingQuote(2, '\''))); assert_eq!( split(r#""$""#), - Err(EnvError::EnvParsingOfMissingVariable(2)), + Err(EnvError::EnvParsingOfVariableOnlyBracedName(2)), ); } @@ -1348,10 +1389,8 @@ mod tests_split_iterator { split("\"\\a\""), Err(EnvError::EnvInvalidSequenceBackslashXInMinusS(2, 'a')) ); - assert_eq!( - split("'\\a'"), - Err(EnvError::EnvInvalidSequenceBackslashXInMinusS(2, 'a')) - ); + assert_eq!(split("'\\a'"), Ok(vec![OsString::from("\\a")])); + assert_eq!(split("'\\`'"), Ok(vec![OsString::from("\\`")])); assert_eq!( split(r#""\a""#), Err(EnvError::EnvInvalidSequenceBackslashXInMinusS(2, 'a'))