diff --git a/crates/nu-command/src/system/run_external.rs b/crates/nu-command/src/system/run_external.rs index 4e2457e4a..720cfb606 100644 --- a/crates/nu-command/src/system/run_external.rs +++ b/crates/nu-command/src/system/run_external.rs @@ -501,7 +501,7 @@ impl ExternalCommand { /// Spawn a command without shelling out to an external shell pub fn spawn_simple_command(&self, cwd: &str) -> Result { - let (head, _) = trim_enclosing_quotes(&self.name.item); + let (head, _, _) = trim_enclosing_quotes(&self.name.item); let head = nu_path::expand_to_real_path(head) .to_string_lossy() .to_string(); @@ -509,9 +509,15 @@ impl ExternalCommand { let mut process = std::process::Command::new(&head); for arg in self.args.iter() { - let (trimmed_args, run_glob_expansion) = trim_enclosing_quotes(&arg.item); + // if arg is quoted, like "aa", 'aa', `aa`. + // `as_a_whole` will be true, so nu won't remove the inner quotes. + let (trimmed_args, run_glob_expansion, as_a_whole) = trim_enclosing_quotes(&arg.item); let mut arg = Spanned { - item: remove_quotes(trimmed_args), + item: if as_a_whole { + trimmed_args + } else { + remove_quotes(trimmed_args) + }, span: arg.span, }; @@ -634,14 +640,18 @@ fn shell_arg_escape(arg: &str) -> String { } } -fn trim_enclosing_quotes(input: &str) -> (String, bool) { +/// This function returns a tuple with 3 items: +/// 1st item: trimmed string. +/// 2nd item: a boolean value indicate if it's ok to run glob expansion. +/// 3rd item: a boolean value indicate if we need to make input as a whole. +fn trim_enclosing_quotes(input: &str) -> (String, bool, bool) { let mut chars = input.chars(); match (chars.next(), chars.next_back()) { - (Some('"'), Some('"')) => (chars.collect(), false), - (Some('\''), Some('\'')) => (chars.collect(), false), - (Some('`'), Some('`')) => (chars.collect(), true), - _ => (input.to_string(), true), + (Some('"'), Some('"')) => (chars.collect(), false, true), + (Some('\''), Some('\'')) => (chars.collect(), false, true), + (Some('`'), Some('`')) => (chars.collect(), true, true), + _ => (input.to_string(), true, false), } } diff --git a/crates/nu-command/tests/commands/run_external.rs b/crates/nu-command/tests/commands/run_external.rs index 08853de0e..a554b1d4b 100644 --- a/crates/nu-command/tests/commands/run_external.rs +++ b/crates/nu-command/tests/commands/run_external.rs @@ -138,6 +138,36 @@ fn failed_command_with_semicolon_will_not_execute_following_cmds() { }) } +#[cfg(not(windows))] +#[test] +fn external_args_with_quoted() { + Playground::setup("external failed command with semicolon", |dirs, _| { + let actual = nu!( + cwd: dirs.test(), pipeline( + r#" + ^echo "foo=bar 'hi'" + "# + )); + + assert_eq!(actual.out, "foo=bar 'hi'"); + }) +} + +#[cfg(not(windows))] +#[test] +fn external_arg_with_long_flag_value_quoted() { + Playground::setup("external failed command with semicolon", |dirs, _| { + let actual = nu!( + cwd: dirs.test(), pipeline( + r#" + ^echo --foo='bar' + "# + )); + + assert_eq!(actual.out, "--foo=bar"); + }) +} + #[cfg(windows)] #[test] fn explicit_glob_windows() {