diff --git a/crates/nu-command/tests/commands/redirection.rs b/crates/nu-command/tests/commands/redirection.rs index 108deb7843b6..f26180eff4ba 100644 --- a/crates/nu-command/tests/commands/redirection.rs +++ b/crates/nu-command/tests/commands/redirection.rs @@ -171,6 +171,22 @@ fn redirection_keep_exit_codes() { }); } +#[test] +fn redirection_stderr_with_failed_program() { + Playground::setup("redirection stderr with failed program", |dirs, _| { + let out = nu!( + cwd: dirs.test(), + r#"$env.FOO = "bar"; nu --testbin echo_env_stderr_fail FOO e> file.txt; echo 3"# + ); + // firstly echo 3 shouldn't run, because previous command runs to failed. + // second `file.txt` should contain "bar". + assert!(!out.out.contains('3')); + let expected_file = dirs.test().join("file.txt"); + let actual = file_contents(expected_file); + assert_eq!(actual, "bar\n"); + }); +} + #[test] fn redirection_with_non_zero_exit_code_should_stop_from_running() { Playground::setup("redirection with non zero exit code", |dirs, _| { diff --git a/crates/nu-engine/src/eval.rs b/crates/nu-engine/src/eval.rs index b0d32fe33ed3..ccf49aaf5847 100644 --- a/crates/nu-engine/src/eval.rs +++ b/crates/nu-engine/src/eval.rs @@ -383,7 +383,21 @@ pub fn eval_expression_with_input( } }; - Ok(might_consume_external_result(input)) + // Given input is PipelineData::ExternalStream + // `might_consume_external_result` will consume `stderr` stream if `stdout` is empty. + // it's not intended if user want to redirect stderr message. + // + // e.g: + // 1. cargo check e>| less + // 2. cargo check e> result.txt + // + // In these two cases, stdout will be empty, but nushell shouldn't consume the `stderr` + // stream it needs be passed to next command. + if !redirect_stderr { + Ok(might_consume_external_result(input)) + } else { + Ok((input, false)) + } } // Try to catch and detect if external command runs to failed. diff --git a/src/main.rs b/src/main.rs index 84fab009bd86..387e5c4fccd4 100644 --- a/src/main.rs +++ b/src/main.rs @@ -269,6 +269,7 @@ fn main() -> Result<()> { match testbin.item.as_str() { "echo_env" => test_bins::echo_env(true), "echo_env_stderr" => test_bins::echo_env(false), + "echo_env_stderr_fail" => test_bins::echo_env_and_fail(false), "echo_env_mixed" => test_bins::echo_env_mixed(), "cococo" => test_bins::cococo(), "meow" => test_bins::meow(), diff --git a/src/test_bins.rs b/src/test_bins.rs index a26ce19dfd40..23d5fd8fa910 100644 --- a/src/test_bins.rs +++ b/src/test_bins.rs @@ -16,6 +16,11 @@ pub fn echo_env(to_stdout: bool) { } } +pub fn echo_env_and_fail(to_stdout: bool) { + echo_env(to_stdout); + fail(); +} + fn echo_one_env(arg: &str, to_stdout: bool) { if let Ok(v) = std::env::var(arg) { if to_stdout { diff --git a/tests/shell/pipeline/commands/external.rs b/tests/shell/pipeline/commands/external.rs index 96ceccab960c..103766e52fda 100644 --- a/tests/shell/pipeline/commands/external.rs +++ b/tests/shell/pipeline/commands/external.rs @@ -158,6 +158,14 @@ fn basic_outerr_pipe_works() { assert_eq!(actual.out, "8"); } +#[test] +fn err_pipe_with_failed_external_works() { + let actual = + nu!(r#"with-env [FOO "bar"] { nu --testbin echo_env_stderr_fail FOO e>| str length }"#); + // there is a `newline` output from nu --testbin + assert_eq!(actual.out, "4"); +} + mod it_evaluation { use super::nu; use nu_test_support::fs::Stub::{EmptyFile, FileWithContent, FileWithContentToBeTrimmed};