Stdout/Stderr redirection (#7185)

This adds new pipeline connectors called out> and err> which redirect either stdout or stderr to a file. You can also use out+err> (or err+out>) to redirect both streams into a file.
This commit is contained in:
JT
2022-11-23 07:26:13 +13:00
committed by GitHub
parent c9f9078726
commit 74a73f9838
15 changed files with 856 additions and 346 deletions

View File

@ -2,8 +2,8 @@ use crate::{current_dir_str, get_full_help, scope::create_scope};
use nu_path::expand_path_with;
use nu_protocol::{
ast::{
Assignment, Bits, Block, Boolean, Call, Comparison, Expr, Expression, Math, Operator,
PathMember, PipelineElement,
Argument, Assignment, Bits, Block, Boolean, Call, Comparison, Expr, Expression, Math,
Operator, PathMember, PipelineElement, Redirection,
},
engine::{EngineState, Stack},
Config, HistoryFileFormat, IntoInterruptiblePipelineData, IntoPipelineData, ListStream,
@ -801,7 +801,7 @@ pub fn eval_element_with_input(
redirect_stderr: bool,
) -> Result<(PipelineData, bool), ShellError> {
match element {
PipelineElement::Expression(expr) => eval_expression_with_input(
PipelineElement::Expression(_, expr) => eval_expression_with_input(
engine_state,
stack,
expr,
@ -809,7 +809,98 @@ pub fn eval_element_with_input(
redirect_stdout,
redirect_stderr,
),
PipelineElement::Redirect(expr) => eval_expression_with_input(
PipelineElement::Redirection(span, redirection, expr) => match &expr.expr {
Expr::String(_) => {
let input = match (redirection, input) {
(
Redirection::Stderr,
PipelineData::ExternalStream {
stderr,
exit_code,
span,
metadata,
..
},
) => PipelineData::ExternalStream {
stdout: stderr,
stderr: None,
exit_code,
span,
metadata,
},
(
Redirection::StdoutAndStderr,
PipelineData::ExternalStream {
stdout,
stderr,
exit_code,
span,
metadata,
},
) => match (stdout, stderr) {
(Some(stdout), Some(stderr)) => PipelineData::ExternalStream {
stdout: Some(stdout.chain(stderr)),
stderr: None,
exit_code,
span,
metadata,
},
(None, Some(stderr)) => PipelineData::ExternalStream {
stdout: Some(stderr),
stderr: None,
exit_code,
span,
metadata,
},
(Some(stdout), None) => PipelineData::ExternalStream {
stdout: Some(stdout),
stderr: None,
exit_code,
span,
metadata,
},
(None, None) => PipelineData::ExternalStream {
stdout: None,
stderr: None,
exit_code,
span,
metadata,
},
},
(_, input) => input,
};
if let Some(save_command) = engine_state.find_decl(b"save", &[]) {
eval_call(
engine_state,
stack,
&Call {
decl_id: save_command,
head: *span,
arguments: vec![
Argument::Positional(expr.clone()),
Argument::Named((
Spanned {
item: "--raw".into(),
span: *span,
},
None,
None,
)),
],
redirect_stdout: false,
redirect_stderr: false,
},
input,
)
.map(|x| (x, false))
} else {
Err(ShellError::CommandNotFound(*span))
}
}
_ => Err(ShellError::CommandNotFound(*span)),
},
PipelineElement::And(_, expr) => eval_expression_with_input(
engine_state,
stack,
expr,
@ -817,15 +908,7 @@ pub fn eval_element_with_input(
redirect_stdout,
redirect_stderr,
),
PipelineElement::And(expr) => eval_expression_with_input(
engine_state,
stack,
expr,
input,
redirect_stdout,
redirect_stderr,
),
PipelineElement::Or(expr) => eval_expression_with_input(
PipelineElement::Or(_, expr) => eval_expression_with_input(
engine_state,
stack,
expr,
@ -846,15 +929,23 @@ pub fn eval_block(
) -> Result<PipelineData, ShellError> {
let num_pipelines = block.len();
for (pipeline_idx, pipeline) in block.pipelines.iter().enumerate() {
for (i, elem) in pipeline.elements.iter().enumerate() {
let mut i = 0;
while i < pipeline.elements.len() {
// if eval internal command failed, it can just make early return with `Err(ShellError)`.
let eval_result = eval_element_with_input(
engine_state,
stack,
elem,
&pipeline.elements[i],
input,
redirect_stdout || (i != pipeline.elements.len() - 1),
redirect_stderr,
redirect_stderr
|| ((i < pipeline.elements.len() - 1)
&& (matches!(
pipeline.elements[i + 1],
PipelineElement::Redirection(_, Redirection::Stderr, _)
| PipelineElement::Redirection(_, Redirection::StdoutAndStderr, _)
))),
)?;
input = eval_result.0;
// external command may runs to failed
@ -863,6 +954,8 @@ pub fn eval_block(
if eval_result.1 {
return Ok(input);
}
i += 1;
}
if pipeline_idx < (num_pipelines) - 1 {