diff --git a/brush-core/benches/shell.rs b/brush-core/benches/shell.rs index ed29401b..83b133c5 100644 --- a/brush-core/benches/shell.rs +++ b/brush-core/benches/shell.rs @@ -26,7 +26,8 @@ mod unix { } async fn expand_string(shell: &mut brush_core::Shell, s: &str) { - let _ = shell.basic_expand_string(s).await.unwrap(); + let params = shell.default_exec_params(); + let _ = shell.basic_expand_string(¶ms, s).await.unwrap(); } fn eval_arithmetic_expr(shell: &mut brush_core::Shell, expr: &str) { diff --git a/brush-core/src/arithmetic.rs b/brush-core/src/arithmetic.rs index 0dfe413f..ccd08521 100644 --- a/brush-core/src/arithmetic.rs +++ b/brush-core/src/arithmetic.rs @@ -1,6 +1,6 @@ use std::borrow::Cow; -use crate::{env, expansion, variables, Shell}; +use crate::{env, expansion, variables, ExecutionParameters, Shell}; use brush_parser::ast; /// Represents an error that occurs during evaluation of an arithmetic expression. @@ -47,12 +47,22 @@ pub trait ExpandAndEvaluate { /// /// * `shell` - The shell to use for evaluation. /// * `trace_if_needed` - Whether to trace the evaluation. - async fn eval(&self, shell: &mut Shell, trace_if_needed: bool) -> Result; + async fn eval( + &self, + shell: &mut Shell, + params: &ExecutionParameters, + trace_if_needed: bool, + ) -> Result; } impl ExpandAndEvaluate for ast::UnexpandedArithmeticExpr { - async fn eval(&self, shell: &mut Shell, trace_if_needed: bool) -> Result { - expand_and_eval(shell, self.value.as_str(), trace_if_needed).await + async fn eval( + &self, + shell: &mut Shell, + params: &ExecutionParameters, + trace_if_needed: bool, + ) -> Result { + expand_and_eval(shell, params, self.value.as_str(), trace_if_needed).await } } @@ -65,11 +75,12 @@ impl ExpandAndEvaluate for ast::UnexpandedArithmeticExpr { /// * `trace_if_needed` - Whether to trace the evaluation. pub(crate) async fn expand_and_eval( shell: &mut Shell, + params: &ExecutionParameters, expr: &str, trace_if_needed: bool, ) -> Result { // Per documentation, first shell-expand it. - let expanded_self = expansion::basic_expand_str_without_tilde(shell, expr) + let expanded_self = expansion::basic_expand_str_without_tilde(shell, params, expr) .await .map_err(|_e| EvalError::FailedToExpandExpression(expr.to_owned()))?; diff --git a/brush-core/src/builtins/printf.rs b/brush-core/src/builtins/printf.rs index 50b60121..b370af17 100644 --- a/brush-core/src/builtins/printf.rs +++ b/brush-core/src/builtins/printf.rs @@ -24,7 +24,13 @@ impl builtins::Command for PrintfCommand { let result = self.evaluate(&context)?; if let Some(variable_name) = &self.output_variable { - expansion::assign_to_named_parameter(context.shell, variable_name, result).await?; + expansion::assign_to_named_parameter( + context.shell, + &context.params, + variable_name, + result, + ) + .await?; } else { write!(context.stdout(), "{result}")?; context.stdout().flush()?; diff --git a/brush-core/src/builtins/test.rs b/brush-core/src/builtins/test.rs index 532552c5..cb0d4e1e 100644 --- a/brush-core/src/builtins/test.rs +++ b/brush-core/src/builtins/test.rs @@ -1,7 +1,7 @@ use clap::Parser; use std::io::Write; -use crate::{builtins, commands, error, tests, Shell}; +use crate::{builtins, commands, error, tests, ExecutionParameters, Shell}; /// Evaluate test expression. #[derive(Parser)] @@ -30,7 +30,7 @@ impl builtins::Command for TestCommand { args = &args[0..args.len() - 1]; } - if execute_test(context.shell, args)? { + if execute_test(context.shell, &context.params, args)? { Ok(builtins::ExitCode::Success) } else { Ok(builtins::ExitCode::Custom(1)) @@ -38,8 +38,12 @@ impl builtins::Command for TestCommand { } } -fn execute_test(shell: &mut Shell, args: &[String]) -> Result { +fn execute_test( + shell: &mut Shell, + params: &ExecutionParameters, + args: &[String], +) -> Result { let test_command = brush_parser::test_command::parse(args).map_err(error::Error::TestCommandParseError)?; - tests::eval_test_expr(&test_command, shell) + tests::eval_test_expr(&test_command, shell, params) } diff --git a/brush-core/src/commands.rs b/brush-core/src/commands.rs index 46b4907e..9b56fc12 100644 --- a/brush-core/src/commands.rs +++ b/brush-core/src/commands.rs @@ -521,7 +521,7 @@ pub(crate) async fn invoke_shell_function( // Apply any redirects specified at function definition-time. if let Some(redirects) = redirects { for redirect in &redirects.0 { - interp::setup_redirect(&mut context.params.open_files, context.shell, redirect).await?; + interp::setup_redirect(context.shell, &mut context.params, redirect).await?; } } @@ -563,21 +563,23 @@ pub(crate) async fn invoke_shell_function( pub(crate) async fn invoke_command_in_subshell_and_get_output( shell: &mut Shell, + params: &ExecutionParameters, s: String, ) -> Result { // Instantiate a subshell to run the command in. let mut subshell = shell.clone(); + // Get our own set of parameters we can customize and use. + let mut params = params.clone(); + params.process_group_policy = ProcessGroupPolicy::SameProcessGroup; + // Set up pipe so we can read the output. let (reader, writer) = sys::pipes::pipe()?; - subshell + params .open_files .files .insert(1, openfiles::OpenFile::PipeWriter(writer)); - let mut params = subshell.default_exec_params(); - params.process_group_policy = ProcessGroupPolicy::SameProcessGroup; - // Run the command. let result = subshell.run_string(s, ¶ms).await?; diff --git a/brush-core/src/completion.rs b/brush-core/src/completion.rs index 8c3d7cc9..030ea668 100644 --- a/brush-core/src/completion.rs +++ b/brush-core/src/completion.rs @@ -244,7 +244,9 @@ impl Spec { // Generate completions based on any provided actions (and on words). let mut candidates = self.generate_action_completions(shell, context).await?; if let Some(word_list) = &self.word_list { - let words = crate::expansion::full_expand_and_split_str(shell, word_list).await?; + let params = shell.default_exec_params(); + let words = + crate::expansion::full_expand_and_split_str(shell, ¶ms, word_list).await?; for word in words { if word.starts_with(context.token_to_complete) { candidates.insert(word); @@ -607,8 +609,10 @@ impl Spec { } // Run the command. + let params = shell.default_exec_params(); let output = - commands::invoke_command_in_subshell_and_get_output(&mut shell, command_line).await?; + commands::invoke_command_in_subshell_and_get_output(&mut shell, ¶ms, command_line) + .await?; // Split results. let mut candidates = IndexSet::new(); @@ -1052,8 +1056,9 @@ async fn get_file_completions( ) -> IndexSet { // Basic-expand the token-to-be-completed; it won't have been expanded to this point. let mut throwaway_shell = shell.clone(); + let params = throwaway_shell.default_exec_params(); let expanded_token = throwaway_shell - .basic_expand_string(token_to_complete) + .basic_expand_string(¶ms, token_to_complete) .await .unwrap_or_else(|_err| token_to_complete.to_owned()); diff --git a/brush-core/src/expansion.rs b/brush-core/src/expansion.rs index 6424f716..a24b32f1 100644 --- a/brush-core/src/expansion.rs +++ b/brush-core/src/expansion.rs @@ -20,6 +20,7 @@ use crate::trace_categories; use crate::variables::ShellValueUnsetType; use crate::variables::ShellVariable; use crate::variables::{self, ShellValue}; +use crate::ExecutionParameters; #[derive(Debug)] struct Expansion { @@ -294,17 +295,19 @@ enum ParameterState { pub(crate) async fn basic_expand_pattern( shell: &mut Shell, + params: &ExecutionParameters, word: &ast::Word, ) -> Result { - let mut expander = WordExpander::new(shell); + let mut expander = WordExpander::new(shell, params); expander.basic_expand_pattern(&word.flatten()).await } pub(crate) async fn basic_expand_regex( shell: &mut Shell, + params: &ExecutionParameters, word: &ast::Word, ) -> Result { - let mut expander = WordExpander::new(shell); + let mut expander = WordExpander::new(shell, params); // Brace expansion does not appear to be used in regexes. expander.force_disable_brace_expansion = true; @@ -314,63 +317,73 @@ pub(crate) async fn basic_expand_regex( pub(crate) async fn basic_expand_word( shell: &mut Shell, + params: &ExecutionParameters, word: &ast::Word, ) -> Result { - basic_expand_str(shell, word.flatten().as_str()).await + basic_expand_str(shell, params, word.flatten().as_str()).await } -pub(crate) async fn basic_expand_str(shell: &mut Shell, s: &str) -> Result { - let mut expander = WordExpander::new(shell); +pub(crate) async fn basic_expand_str( + shell: &mut Shell, + params: &ExecutionParameters, + s: &str, +) -> Result { + let mut expander = WordExpander::new(shell, params); expander.basic_expand_to_str(s).await } pub(crate) async fn basic_expand_str_without_tilde( shell: &mut Shell, + params: &ExecutionParameters, s: &str, ) -> Result { - let mut expander = WordExpander::new(shell); + let mut expander = WordExpander::new(shell, params); expander.parser_options.tilde_expansion = false; expander.basic_expand_to_str(s).await } pub(crate) async fn full_expand_and_split_word( shell: &mut Shell, + params: &ExecutionParameters, word: &ast::Word, ) -> Result, error::Error> { - full_expand_and_split_str(shell, word.flatten().as_str()).await + full_expand_and_split_str(shell, params, word.flatten().as_str()).await } pub(crate) async fn full_expand_and_split_str( shell: &mut Shell, + params: &ExecutionParameters, s: &str, ) -> Result, error::Error> { - let mut expander = WordExpander::new(shell); + let mut expander = WordExpander::new(shell, params); expander.full_expand_with_splitting(s).await } pub(crate) async fn assign_to_named_parameter( shell: &mut Shell, + params: &ExecutionParameters, name: &str, value: String, ) -> Result<(), error::Error> { let parser_options = shell.parser_options(); - let mut expander = WordExpander::new(shell); + let mut expander = WordExpander::new(shell, params); let parameter = brush_parser::word::parse_parameter(name, &parser_options)?; expander.assign_to_parameter(¶meter, value).await } struct WordExpander<'a> { shell: &'a mut Shell, + params: &'a ExecutionParameters, parser_options: brush_parser::ParserOptions, force_disable_brace_expansion: bool, } impl<'a> WordExpander<'a> { - pub fn new(shell: &'a mut Shell) -> Self { + pub fn new(shell: &'a mut Shell, params: &'a ExecutionParameters) -> Self { let parser_options = shell.parser_options(); - Self { shell, + params, parser_options, force_disable_brace_expansion: false, } @@ -710,7 +723,8 @@ impl<'a> WordExpander<'a> { brush_parser::word::WordPiece::BackquotedCommandSubstitution(s) | brush_parser::word::WordPiece::CommandSubstitution(s) => { let output_str = - commands::invoke_command_in_subshell_and_get_output(self.shell, s).await?; + commands::invoke_command_in_subshell_and_get_output(self.shell, self.params, s) + .await?; // We trim trailing newlines, per spec. let trimmed = output_str.trim_end_matches('\n'); @@ -923,7 +937,7 @@ impl<'a> WordExpander<'a> { ); } - let expanded_offset = offset.eval(self.shell, false).await?; + let expanded_offset = offset.eval(self.shell, self.params, false).await?; let expanded_offset = if expanded_offset < 0 { 0 } else { @@ -934,7 +948,7 @@ impl<'a> WordExpander<'a> { let expanded_offset = min(expanded_offset, expanded_parameter_len); let end_offset = if let Some(length) = length { - let mut expanded_length = length.eval(self.shell, false).await?; + let mut expanded_length = length.eval(self.shell, self.params, false).await?; if expanded_length < 0 { let param_length: i64 = i64::try_from(expanded_parameter_len)?; expanded_length += param_length; @@ -1357,7 +1371,7 @@ impl<'a> WordExpander<'a> { let index_to_use = if for_set_associative_array { self.basic_expand_to_str(index).await? } else { - arithmetic::expand_and_eval(self.shell, index, false) + arithmetic::expand_and_eval(self.shell, self.params, index, false) .await? .to_string() }; @@ -1416,7 +1430,7 @@ impl<'a> WordExpander<'a> { &mut self, expr: brush_parser::ast::UnexpandedArithmeticExpr, ) -> Result { - let value = expr.eval(self.shell, false).await?; + let value = expr.eval(self.shell, self.params, false).await?; Ok(value.to_string()) } @@ -1691,33 +1705,34 @@ mod tests { async fn test_full_expansion() -> Result<()> { let options = crate::shell::CreateOptions::default(); let mut shell = crate::shell::Shell::new(&options).await?; + let params = shell.default_exec_params(); assert_eq!( - full_expand_and_split_str(&mut shell, "\"\"").await?, + full_expand_and_split_str(&mut shell, ¶ms, "\"\"").await?, vec![""] ); assert_eq!( - full_expand_and_split_str(&mut shell, "a b").await?, + full_expand_and_split_str(&mut shell, ¶ms, "a b").await?, vec!["a", "b"] ); assert_eq!( - full_expand_and_split_str(&mut shell, "ab").await?, + full_expand_and_split_str(&mut shell, ¶ms, "ab").await?, vec!["ab"] ); assert_eq!( - full_expand_and_split_str(&mut shell, r#""a b""#).await?, + full_expand_and_split_str(&mut shell, ¶ms, r#""a b""#).await?, vec!["a b"] ); assert_eq!( - full_expand_and_split_str(&mut shell, "").await?, + full_expand_and_split_str(&mut shell, ¶ms, "").await?, Vec::::new() ); assert_eq!( - full_expand_and_split_str(&mut shell, "$@").await?, + full_expand_and_split_str(&mut shell, ¶ms, "$@").await?, Vec::::new() ); assert_eq!( - full_expand_and_split_str(&mut shell, "$*").await?, + full_expand_and_split_str(&mut shell, ¶ms, "$*").await?, Vec::::new() ); @@ -1728,7 +1743,8 @@ mod tests { async fn test_brace_expansion() -> Result<()> { let options = crate::shell::CreateOptions::default(); let mut shell = crate::shell::Shell::new(&options).await?; - let expander = WordExpander::new(&mut shell); + let params = shell.default_exec_params(); + let expander = WordExpander::new(&mut shell, ¶ms); assert_eq!(expander.brace_expand_if_needed("abc")?, ["abc"]); assert_eq!(expander.brace_expand_if_needed("a{,b}d")?, ["ad", "abd"]); @@ -1751,7 +1767,8 @@ mod tests { async fn test_field_splitting() -> Result<()> { let options = crate::shell::CreateOptions::default(); let mut shell = crate::shell::Shell::new(&options).await?; - let expander = WordExpander::new(&mut shell); + let params = shell.default_exec_params(); + let expander = WordExpander::new(&mut shell, ¶ms); let expansion = Expansion { fields: vec![ diff --git a/brush-core/src/extendedtests.rs b/brush-core/src/extendedtests.rs index 1c180714..cc54e2b3 100644 --- a/brush-core/src/extendedtests.rs +++ b/brush-core/src/extendedtests.rs @@ -8,37 +8,40 @@ use crate::{ users, }, variables::{self, ArrayLiteral}, - Shell, + ExecutionParameters, Shell, }; #[async_recursion::async_recursion] pub(crate) async fn eval_extended_test_expr( expr: &ast::ExtendedTestExpr, shell: &mut Shell, + params: &ExecutionParameters, ) -> Result { #[allow(clippy::single_match_else)] match expr { ast::ExtendedTestExpr::UnaryTest(op, operand) => { - apply_unary_predicate(op, operand, shell).await + apply_unary_predicate(op, operand, shell, params).await } ast::ExtendedTestExpr::BinaryTest(op, left, right) => { - apply_binary_predicate(op, left, right, shell).await + apply_binary_predicate(op, left, right, shell, params).await } ast::ExtendedTestExpr::And(left, right) => { - let result = eval_extended_test_expr(left, shell).await? - && eval_extended_test_expr(right, shell).await?; + let result = eval_extended_test_expr(left, shell, params).await? + && eval_extended_test_expr(right, shell, params).await?; Ok(result) } ast::ExtendedTestExpr::Or(left, right) => { - let result = eval_extended_test_expr(left, shell).await? - || eval_extended_test_expr(right, shell).await?; + let result = eval_extended_test_expr(left, shell, params).await? + || eval_extended_test_expr(right, shell, params).await?; Ok(result) } ast::ExtendedTestExpr::Not(expr) => { - let result = !eval_extended_test_expr(expr, shell).await?; + let result = !eval_extended_test_expr(expr, shell, params).await?; Ok(result) } - ast::ExtendedTestExpr::Parenthesized(expr) => eval_extended_test_expr(expr, shell).await, + ast::ExtendedTestExpr::Parenthesized(expr) => { + eval_extended_test_expr(expr, shell, params).await + } } } @@ -46,8 +49,9 @@ async fn apply_unary_predicate( op: &ast::UnaryPredicate, operand: &ast::Word, shell: &mut Shell, + params: &ExecutionParameters, ) -> Result { - let expanded_operand = expansion::basic_expand_word(shell, operand).await?; + let expanded_operand = expansion::basic_expand_word(shell, params, operand).await?; if shell.options.print_commands_and_arguments { shell.trace_command(std::format!( @@ -56,7 +60,7 @@ async fn apply_unary_predicate( ))?; } - apply_unary_predicate_to_str(op, expanded_operand.as_str(), shell) + apply_unary_predicate_to_str(op, expanded_operand.as_str(), shell, params) } #[allow(clippy::too_many_lines)] @@ -64,6 +68,7 @@ pub(crate) fn apply_unary_predicate_to_str( op: &ast::UnaryPredicate, operand: &str, shell: &mut Shell, + params: &ExecutionParameters, ) -> Result { #[allow(clippy::match_single_binding)] match op { @@ -119,7 +124,7 @@ pub(crate) fn apply_unary_predicate_to_str( } ast::UnaryPredicate::FdIsOpenTerminal => { if let Ok(fd) = operand.parse::() { - if let Some(open_file) = shell.open_files.files.get(&fd) { + if let Some(open_file) = params.open_files.files.get(&fd) { Ok(open_file.is_term()) } else { Ok(false) @@ -186,12 +191,13 @@ async fn apply_binary_predicate( left: &ast::Word, right: &ast::Word, shell: &mut Shell, + params: &ExecutionParameters, ) -> Result { #[allow(clippy::single_match_else)] match op { ast::BinaryPredicate::StringMatchesRegex => { - let s = expansion::basic_expand_word(shell, left).await?; - let regex = expansion::basic_expand_regex(shell, right).await?; + let s = expansion::basic_expand_word(shell, params, left).await?; + let regex = expansion::basic_expand_regex(shell, params, right).await?; if shell.options.print_commands_and_arguments { shell.trace_command(std::format!("[[ {s} {op} {right} ]]"))?; @@ -227,8 +233,8 @@ async fn apply_binary_predicate( Ok(matches) } ast::BinaryPredicate::StringExactlyMatchesString => { - let left = expansion::basic_expand_word(shell, left).await?; - let right = expansion::basic_expand_word(shell, right).await?; + let left = expansion::basic_expand_word(shell, params, left).await?; + let right = expansion::basic_expand_word(shell, params, right).await?; if shell.options.print_commands_and_arguments { shell.trace_command(std::format!("[[ {left} {op} {right} ]]"))?; @@ -237,8 +243,8 @@ async fn apply_binary_predicate( Ok(left == right) } ast::BinaryPredicate::StringDoesNotExactlyMatchString => { - let left = expansion::basic_expand_word(shell, left).await?; - let right = expansion::basic_expand_word(shell, right).await?; + let left = expansion::basic_expand_word(shell, params, left).await?; + let right = expansion::basic_expand_word(shell, params, right).await?; if shell.options.print_commands_and_arguments { shell.trace_command(std::format!("[[ {left} {op} {right} ]]"))?; @@ -247,8 +253,8 @@ async fn apply_binary_predicate( Ok(left != right) } ast::BinaryPredicate::StringContainsSubstring => { - let s = expansion::basic_expand_word(shell, left).await?; - let substring = expansion::basic_expand_word(shell, right).await?; + let s = expansion::basic_expand_word(shell, params, left).await?; + let substring = expansion::basic_expand_word(shell, params, right).await?; if shell.options.print_commands_and_arguments { shell.trace_command(std::format!("[[ {s} {op} {substring} ]]"))?; @@ -266,8 +272,8 @@ async fn apply_binary_predicate( "extended test binary predicate LeftFileIsOlderOrDoesNotExistWhenRightDoes", ), ast::BinaryPredicate::LeftSortsBeforeRight => { - let left = expansion::basic_expand_word(shell, left).await?; - let right = expansion::basic_expand_word(shell, right).await?; + let left = expansion::basic_expand_word(shell, params, left).await?; + let right = expansion::basic_expand_word(shell, params, right).await?; if shell.options.print_commands_and_arguments { shell.trace_command(std::format!("[[ {left} {op} {right} ]]"))?; @@ -277,8 +283,8 @@ async fn apply_binary_predicate( Ok(left < right) } ast::BinaryPredicate::LeftSortsAfterRight => { - let left = expansion::basic_expand_word(shell, left).await?; - let right = expansion::basic_expand_word(shell, right).await?; + let left = expansion::basic_expand_word(shell, params, left).await?; + let right = expansion::basic_expand_word(shell, params, right).await?; if shell.options.print_commands_and_arguments { shell.trace_command(std::format!("[[ {left} {op} {right} ]]"))?; @@ -288,8 +294,10 @@ async fn apply_binary_predicate( Ok(left > right) } ast::BinaryPredicate::ArithmeticEqualTo => { - let left = arithmetic::expand_and_eval(shell, left.value.as_str(), false).await?; - let right = arithmetic::expand_and_eval(shell, right.value.as_str(), false).await?; + let left = + arithmetic::expand_and_eval(shell, params, left.value.as_str(), false).await?; + let right = + arithmetic::expand_and_eval(shell, params, right.value.as_str(), false).await?; if shell.options.print_commands_and_arguments { shell.trace_command(std::format!("[[ {left} {op} {right} ]]"))?; @@ -298,8 +306,10 @@ async fn apply_binary_predicate( Ok(left == right) } ast::BinaryPredicate::ArithmeticNotEqualTo => { - let left = arithmetic::expand_and_eval(shell, left.value.as_str(), false).await?; - let right = arithmetic::expand_and_eval(shell, right.value.as_str(), false).await?; + let left = + arithmetic::expand_and_eval(shell, params, left.value.as_str(), false).await?; + let right = + arithmetic::expand_and_eval(shell, params, right.value.as_str(), false).await?; if shell.options.print_commands_and_arguments { shell.trace_command(std::format!("[[ {left} {op} {right} ]]"))?; @@ -308,8 +318,10 @@ async fn apply_binary_predicate( Ok(left != right) } ast::BinaryPredicate::ArithmeticLessThan => { - let left = arithmetic::expand_and_eval(shell, left.value.as_str(), false).await?; - let right = arithmetic::expand_and_eval(shell, right.value.as_str(), false).await?; + let left = + arithmetic::expand_and_eval(shell, params, left.value.as_str(), false).await?; + let right = + arithmetic::expand_and_eval(shell, params, right.value.as_str(), false).await?; if shell.options.print_commands_and_arguments { shell.trace_command(std::format!("[[ {left} {op} {right} ]]"))?; @@ -318,8 +330,10 @@ async fn apply_binary_predicate( Ok(left < right) } ast::BinaryPredicate::ArithmeticLessThanOrEqualTo => { - let left = arithmetic::expand_and_eval(shell, left.value.as_str(), false).await?; - let right = arithmetic::expand_and_eval(shell, right.value.as_str(), false).await?; + let left = + arithmetic::expand_and_eval(shell, params, left.value.as_str(), false).await?; + let right = + arithmetic::expand_and_eval(shell, params, right.value.as_str(), false).await?; if shell.options.print_commands_and_arguments { shell.trace_command(std::format!("[[ {left} {op} {right} ]]"))?; @@ -328,8 +342,10 @@ async fn apply_binary_predicate( Ok(left <= right) } ast::BinaryPredicate::ArithmeticGreaterThan => { - let left = arithmetic::expand_and_eval(shell, left.value.as_str(), false).await?; - let right = arithmetic::expand_and_eval(shell, right.value.as_str(), false).await?; + let left = + arithmetic::expand_and_eval(shell, params, left.value.as_str(), false).await?; + let right = + arithmetic::expand_and_eval(shell, params, right.value.as_str(), false).await?; if shell.options.print_commands_and_arguments { shell.trace_command(std::format!("[[ {left} {op} {right} ]]"))?; @@ -338,8 +354,10 @@ async fn apply_binary_predicate( Ok(left > right) } ast::BinaryPredicate::ArithmeticGreaterThanOrEqualTo => { - let left = arithmetic::expand_and_eval(shell, left.value.as_str(), false).await?; - let right = arithmetic::expand_and_eval(shell, right.value.as_str(), false).await?; + let left = + arithmetic::expand_and_eval(shell, params, left.value.as_str(), false).await?; + let right = + arithmetic::expand_and_eval(shell, params, right.value.as_str(), false).await?; if shell.options.print_commands_and_arguments { shell.trace_command(std::format!("[[ {left} {op} {right} ]]"))?; @@ -352,14 +370,14 @@ async fn apply_binary_predicate( // operand (treated as a shell pattern). // TODO: implement case-insensitive matching if relevant via shopt options (nocasematch). ast::BinaryPredicate::StringExactlyMatchesPattern => { - let s = expansion::basic_expand_word(shell, left).await?; - let pattern = expansion::basic_expand_pattern(shell, right) + let s = expansion::basic_expand_word(shell, params, left).await?; + let pattern = expansion::basic_expand_pattern(shell, params, right) .await? .set_extended_globbing(shell.options.extended_globbing) .set_case_insensitive(shell.options.case_insensitive_conditionals); if shell.options.print_commands_and_arguments { - let expanded_right = expansion::basic_expand_word(shell, right).await?; + let expanded_right = expansion::basic_expand_word(shell, params, right).await?; let escaped_right = escape::quote_if_needed( expanded_right.as_str(), escape::QuoteMode::BackslashEscape, @@ -370,14 +388,14 @@ async fn apply_binary_predicate( pattern.exactly_matches(s.as_str()) } ast::BinaryPredicate::StringDoesNotExactlyMatchPattern => { - let s = expansion::basic_expand_word(shell, left).await?; - let pattern = expansion::basic_expand_pattern(shell, right) + let s = expansion::basic_expand_word(shell, params, left).await?; + let pattern = expansion::basic_expand_pattern(shell, params, right) .await? .set_extended_globbing(shell.options.extended_globbing) .set_case_insensitive(shell.options.case_insensitive_conditionals); if shell.options.print_commands_and_arguments { - let expanded_right = expansion::basic_expand_word(shell, right).await?; + let expanded_right = expansion::basic_expand_word(shell, params, right).await?; let escaped_right = escape::quote_if_needed( expanded_right.as_str(), escape::QuoteMode::BackslashEscape, diff --git a/brush-core/src/interp.rs b/brush-core/src/interp.rs index cb8a2432..9885fe8d 100644 --- a/brush-core/src/interp.rs +++ b/brush-core/src/interp.rs @@ -482,8 +482,7 @@ impl ExecuteInPipeline for ast::Command { // Set up any additional redirects. if let Some(redirects) = redirects { for redirect in &redirects.0 { - setup_redirect(&mut params.open_files, pipeline_context.shell, redirect) - .await?; + setup_redirect(pipeline_context.shell, &mut params, redirect).await?; } } @@ -508,7 +507,9 @@ impl ExecuteInPipeline for ast::Command { } ast::Command::ExtendedTest(e) => { let result = - if extendedtests::eval_extended_test_expr(e, pipeline_context.shell).await? { + if extendedtests::eval_extended_test_expr(e, pipeline_context.shell, ¶ms) + .await? + { 0 } else { 1 @@ -573,7 +574,8 @@ impl Execute for ast::ForClauseCommand { let mut expanded_values = vec![]; if let Some(unexpanded_values) = &self.values { for value in unexpanded_values { - let mut expanded = expansion::full_expand_and_split_word(shell, value).await?; + let mut expanded = + expansion::full_expand_and_split_word(shell, params, value).await?; expanded_values.append(&mut expanded); } } else { @@ -644,7 +646,7 @@ impl Execute for ast::CaseClauseCommand { shell.trace_command(std::format!("case {} in", &self.value))?; } - let expanded_value = expansion::basic_expand_word(shell, &self.value).await?; + let expanded_value = expansion::basic_expand_word(shell, params, &self.value).await?; let mut result: ExecutionResult = ExecutionResult::success(); let mut force_execute_next_case = false; @@ -654,7 +656,7 @@ impl Execute for ast::CaseClauseCommand { } else { let mut matches = false; for pattern in &case.patterns { - let expanded_pattern = expansion::basic_expand_pattern(shell, pattern) + let expanded_pattern = expansion::basic_expand_pattern(shell, params, pattern) .await? .set_extended_globbing(shell.options.extended_globbing) .set_case_insensitive(shell.options.case_insensitive_conditionals); @@ -790,9 +792,9 @@ impl Execute for ast::ArithmeticCommand { async fn execute( &self, shell: &mut Shell, - _params: &ExecutionParameters, + params: &ExecutionParameters, ) -> Result { - let value = self.expr.eval(shell, true).await?; + let value = self.expr.eval(shell, params, true).await?; let result = if value != 0 { ExecutionResult::success() } else { @@ -814,12 +816,12 @@ impl Execute for ast::ArithmeticForClauseCommand { ) -> Result { let mut result = ExecutionResult::success(); if let Some(initializer) = &self.initializer { - initializer.eval(shell, true).await?; + initializer.eval(shell, params, true).await?; } loop { if let Some(condition) = &self.condition { - if condition.eval(shell, true).await? == 0 { + if condition.eval(shell, params, true).await? == 0 { break; } } @@ -847,7 +849,7 @@ impl Execute for ast::ArithmeticForClauseCommand { } if let Some(updater) = &self.updater { - updater.eval(shell, true).await?; + updater.eval(shell, params, true).await?; } } @@ -908,7 +910,7 @@ impl ExecuteInPipeline for ast::SimpleCommand { { match item { CommandPrefixOrSuffixItem::IoRedirect(redirect) => { - if setup_redirect(&mut params.open_files, context.shell, redirect) + if setup_redirect(context.shell, &mut params, redirect) .await? .is_none() { @@ -918,8 +920,8 @@ impl ExecuteInPipeline for ast::SimpleCommand { } CommandPrefixOrSuffixItem::ProcessSubstitution(kind, subshell_command) => { let (installed_fd_num, substitution_file) = setup_process_substitution( - &mut params.open_files, context.shell, + &mut params, kind, subshell_command, )?; @@ -943,14 +945,15 @@ impl ExecuteInPipeline for ast::SimpleCommand { // This looks like an assignment, and the command being invoked is a // well-known builtin that takes arguments that need to function like // assignments (but which are processed by the builtin). - let expanded = expand_assignment(context.shell, assignment).await?; + let expanded = + expand_assignment(context.shell, ¶ms, assignment).await?; args.push(CommandArg::Assignment(expanded)); } else { // This *looks* like an assignment, but it's really a string we should // fully treat as a regular looking // argument. let mut next_args = - expansion::full_expand_and_split_word(context.shell, word) + expansion::full_expand_and_split_word(context.shell, ¶ms, word) .await? .into_iter() .map(CommandArg::String) @@ -961,7 +964,7 @@ impl ExecuteInPipeline for ast::SimpleCommand { } CommandPrefixOrSuffixItem::Word(arg) => { let mut next_args = - expansion::full_expand_and_split_word(context.shell, arg).await?; + expansion::full_expand_and_split_word(context.shell, ¶ms, arg).await?; if args.is_empty() { if let Some(cmd_name) = next_args.first() { @@ -1013,6 +1016,7 @@ impl ExecuteInPipeline for ast::SimpleCommand { apply_assignment( assignment, context.shell, + ¶ms, true, Some(EnvironmentScope::Command), EnvironmentScope::Command, @@ -1095,6 +1099,7 @@ impl ExecuteInPipeline for ast::SimpleCommand { apply_assignment( assignment, context.shell, + ¶ms, false, None, EnvironmentScope::Global, @@ -1113,11 +1118,12 @@ impl ExecuteInPipeline for ast::SimpleCommand { async fn expand_assignment( shell: &mut Shell, + params: &ExecutionParameters, assignment: &ast::Assignment, ) -> Result { - let value = expand_assignment_value(shell, &assignment.value).await?; + let value = expand_assignment_value(shell, params, &assignment.value).await?; Ok(ast::Assignment { - name: basic_expand_assignment_name(shell, &assignment.name).await?, + name: basic_expand_assignment_name(shell, params, &assignment.name).await?, value, append: assignment.append, }) @@ -1125,16 +1131,17 @@ async fn expand_assignment( async fn basic_expand_assignment_name( shell: &mut Shell, + params: &ExecutionParameters, name: &ast::AssignmentName, ) -> Result { match name { ast::AssignmentName::VariableName(name) => { - let expanded = expansion::basic_expand_str(shell, name).await?; + let expanded = expansion::basic_expand_str(shell, params, name).await?; Ok(ast::AssignmentName::VariableName(expanded)) } ast::AssignmentName::ArrayElementName(name, index) => { - let expanded_name = expansion::basic_expand_str(shell, name).await?; - let expanded_index = expansion::basic_expand_str(shell, index).await?; + let expanded_name = expansion::basic_expand_str(shell, params, name).await?; + let expanded_index = expansion::basic_expand_str(shell, params, index).await?; Ok(ast::AssignmentName::ArrayElementName( expanded_name, expanded_index, @@ -1145,11 +1152,12 @@ async fn basic_expand_assignment_name( async fn expand_assignment_value( shell: &mut Shell, + params: &ExecutionParameters, value: &ast::AssignmentValue, ) -> Result { let expanded = match value { ast::AssignmentValue::Scalar(s) => { - let expanded_word = expansion::basic_expand_word(shell, s).await?; + let expanded_word = expansion::basic_expand_word(shell, params, s).await?; ast::AssignmentValue::Scalar(ast::Word { value: expanded_word, }) @@ -1158,12 +1166,14 @@ async fn expand_assignment_value( let mut expanded_values = vec![]; for (key, value) in arr { if let Some(k) = key { - let expanded_key = expansion::basic_expand_word(shell, k).await?.into(); - let expanded_value = expansion::basic_expand_word(shell, value).await?.into(); + let expanded_key = expansion::basic_expand_word(shell, params, k).await?.into(); + let expanded_value = expansion::basic_expand_word(shell, params, value) + .await? + .into(); expanded_values.push((Some(expanded_key), expanded_value)); } else { let split_expanded_value = - expansion::full_expand_and_split_word(shell, value).await?; + expansion::full_expand_and_split_word(shell, params, value).await?; for expanded_value in split_expanded_value { expanded_values.push((None, expanded_value.into())); } @@ -1181,6 +1191,7 @@ async fn expand_assignment_value( async fn apply_assignment( assignment: &ast::Assignment, shell: &mut Shell, + params: &ExecutionParameters, mut export: bool, required_scope: Option, creation_scope: EnvironmentScope, @@ -1194,7 +1205,7 @@ async fn apply_assignment( name } ast::AssignmentName::ArrayElementName(name, index) => { - let expanded = expansion::basic_expand_str(shell, index).await?; + let expanded = expansion::basic_expand_str(shell, params, index).await?; array_index = Some(expanded); name } @@ -1203,7 +1214,7 @@ async fn apply_assignment( // Expand the values. let new_value = match &assignment.value { ast::AssignmentValue::Scalar(unexpanded_value) => { - let value = expansion::basic_expand_word(shell, unexpanded_value).await?; + let value = expansion::basic_expand_word(shell, params, unexpanded_value).await?; ShellValueLiteral::Scalar(value) } ast::AssignmentValue::Array(unexpanded_values) => { @@ -1211,17 +1222,19 @@ async fn apply_assignment( for (unexpanded_key, unexpanded_value) in unexpanded_values { let key = match unexpanded_key { Some(unexpanded_key) => { - Some(expansion::basic_expand_word(shell, unexpanded_key).await?) + Some(expansion::basic_expand_word(shell, params, unexpanded_key).await?) } None => None, }; if key.is_some() { - let value = expansion::basic_expand_word(shell, unexpanded_value).await?; + let value = + expansion::basic_expand_word(shell, params, unexpanded_value).await?; elements.push((key, value)); } else { let values = - expansion::full_expand_and_split_word(shell, unexpanded_value).await?; + expansion::full_expand_and_split_word(shell, params, unexpanded_value) + .await?; for value in values { elements.push((None, value)); } @@ -1250,7 +1263,7 @@ async fn apply_assignment( if will_be_indexed_array { array_index = Some( - arithmetic::expand_and_eval(shell, idx.as_str(), false) + arithmetic::expand_and_eval(shell, params, idx.as_str(), false) .await? .to_string(), ); @@ -1349,13 +1362,14 @@ fn setup_pipeline_redirection( #[allow(clippy::too_many_lines)] pub(crate) async fn setup_redirect( - open_files: &'_ mut OpenFiles, shell: &mut Shell, + params: &'_ mut ExecutionParameters, redirect: &ast::IoRedirect, ) -> Result, error::Error> { match redirect { ast::IoRedirect::OutputAndError(f, append) => { - let mut expanded_fields = expansion::full_expand_and_split_word(shell, f).await?; + let mut expanded_fields = + expansion::full_expand_and_split_word(shell, params, f).await?; if expanded_fields.len() != 1 { return Err(error::Error::InvalidRedirection); } @@ -1379,8 +1393,8 @@ pub(crate) async fn setup_redirect( let stdout_file = OpenFile::File(opened_file); let stderr_file = stdout_file.try_dup()?; - open_files.files.insert(1, stdout_file); - open_files.files.insert(2, stderr_file); + params.open_files.files.insert(1, stdout_file); + params.open_files.files.insert(2, stderr_file); Ok(Some(1)) } @@ -1392,7 +1406,7 @@ pub(crate) async fn setup_redirect( let mut options = std::fs::File::options(); let mut expanded_fields = - expansion::full_expand_and_split_word(shell, f).await?; + expansion::full_expand_and_split_word(shell, params, f).await?; if expanded_fields.len() != 1 { return Err(error::Error::InvalidRedirection); @@ -1470,7 +1484,7 @@ pub(crate) async fn setup_redirect( fd_num = specified_fd_num.unwrap_or(default_fd_if_unspecified); - if let Some(f) = open_files.files.get(fd) { + if let Some(f) = params.open_files.files.get(fd) { target_file = f.try_dup()?; } else { tracing::error!("{}: Bad file descriptor", fd); @@ -1485,14 +1499,17 @@ pub(crate) async fn setup_redirect( | ast::IoFileRedirectKind::ReadAndWrite | ast::IoFileRedirectKind::Clobber => { let (substitution_fd, substitution_file) = setup_process_substitution( - open_files, shell, + params, substitution_kind, subshell_cmd, )?; target_file = substitution_file.try_dup()?; - open_files.files.insert(substitution_fd, substitution_file); + params + .open_files + .files + .insert(substitution_fd, substitution_file); fd_num = specified_fd_num .unwrap_or_else(|| get_default_fd_for_redirect_kind(kind)); @@ -1502,7 +1519,7 @@ pub(crate) async fn setup_redirect( } } - open_files.files.insert(fd_num, target_file); + params.open_files.files.insert(fd_num, target_file); Ok(Some(fd_num)) } ast::IoRedirect::HereDocument(fd_num, io_here) => { @@ -1511,26 +1528,26 @@ pub(crate) async fn setup_redirect( // Expand if required. let io_here_doc = if io_here.requires_expansion { - expansion::basic_expand_word(shell, &io_here.doc).await? + expansion::basic_expand_word(shell, params, &io_here.doc).await? } else { io_here.doc.flatten() }; let f = setup_open_file_with_contents(io_here_doc.as_str())?; - open_files.files.insert(fd_num, f); + params.open_files.files.insert(fd_num, f); Ok(Some(fd_num)) } ast::IoRedirect::HereString(fd_num, word) => { // If not specified, default to stdin (fd 0). let fd_num = fd_num.unwrap_or(0); - let mut expanded_word = expansion::basic_expand_word(shell, word).await?; + let mut expanded_word = expansion::basic_expand_word(shell, params, word).await?; expanded_word.push('\n'); let f = setup_open_file_with_contents(expanded_word.as_str())?; - open_files.files.insert(fd_num, f); + params.open_files.files.insert(fd_num, f); Ok(Some(fd_num)) } } @@ -1549,8 +1566,8 @@ fn get_default_fd_for_redirect_kind(kind: &ast::IoFileRedirectKind) -> u32 { } fn setup_process_substitution( - open_files: &mut OpenFiles, shell: &mut Shell, + params: &mut ExecutionParameters, kind: &ast::ProcessSubstitutionKind, subshell_cmd: &ast::SubshellCommand, ) -> Result<(u32, OpenFile), error::Error> { @@ -1558,19 +1575,23 @@ fn setup_process_substitution( // Execute in a subshell. let mut subshell = shell.clone(); + // Set up execution parameters for the child execution. + let mut child_params = params.clone(); + child_params.process_group_policy = ProcessGroupPolicy::SameProcessGroup; + // Set up pipe so we can connect to the command. let (reader, writer) = sys::pipes::pipe()?; let target_file = match kind { ast::ProcessSubstitutionKind::Read => { - subshell + child_params .open_files .files .insert(1, openfiles::OpenFile::PipeWriter(writer)); OpenFile::PipeReader(reader) } ast::ProcessSubstitutionKind::Write => { - subshell + child_params .open_files .files .insert(0, openfiles::OpenFile::PipeReader(reader)); @@ -1578,23 +1599,18 @@ fn setup_process_substitution( } }; - let exec_params = ExecutionParameters { - open_files: subshell.open_files.clone(), - process_group_policy: ProcessGroupPolicy::SameProcessGroup, - }; - // Asynchronously spawn off the subshell; we intentionally don't block on its // completion. let subshell_cmd = subshell_cmd.to_owned(); tokio::spawn(async move { // Intentionally ignore the result of the subshell command. - let _ = subshell_cmd.0.execute(&mut subshell, &exec_params).await; + let _ = subshell_cmd.0.execute(&mut subshell, &child_params).await; }); // Starting at 63 (a.k.a. 64-1)--and decrementing--look for an // available fd. let mut candidate_fd_num = 63; - while open_files.files.contains_key(&candidate_fd_num) { + while params.open_files.files.contains_key(&candidate_fd_num) { candidate_fd_num -= 1; if candidate_fd_num == 0 { return error::unimp("no available file descriptors"); diff --git a/brush-core/src/shell.rs b/brush-core/src/shell.rs index 655c00a5..8cdac2c5 100644 --- a/brush-core/src/shell.rs +++ b/brush-core/src/shell.rs @@ -33,7 +33,7 @@ pub struct Shell { /// Trap handler configuration for the shell. pub traps: traps::TrapHandlerConfig, /// Manages files opened and accessible via redirection operators. - pub open_files: openfiles::OpenFiles, + open_files: openfiles::OpenFiles, /// The current working directory. pub working_dir: PathBuf, /// The shell environment, containing shell variables. @@ -942,9 +942,10 @@ impl Shell { /// * `s` - The string to expand. pub async fn basic_expand_string>( &mut self, + params: &ExecutionParameters, s: S, ) -> Result { - let result = expansion::basic_expand_str(self, s.as_ref()).await?; + let result = expansion::basic_expand_str(self, params, s.as_ref()).await?; Ok(result) } @@ -956,9 +957,10 @@ impl Shell { /// * `s` - The string to expand and split. pub async fn full_expand_and_split_string>( &mut self, + params: &ExecutionParameters, s: S, ) -> Result, error::Error> { - let result = expansion::full_expand_and_split_str(self, s.as_ref()).await?; + let result = expansion::full_expand_and_split_str(self, params, s.as_ref()).await?; Ok(result) } @@ -981,13 +983,9 @@ impl Shell { script_path: &Path, args: I, ) -> Result { - self.parse_and_execute_script_file( - script_path, - args, - &self.default_exec_params(), - ScriptCallType::Executed, - ) - .await + let params = self.default_exec_params(); + self.parse_and_execute_script_file(script_path, args, ¶ms, ScriptCallType::Executed) + .await } async fn run_parsed_result( @@ -1116,7 +1114,8 @@ impl Shell { let formatted_prompt = prompt::expand_prompt(self, prompt_spec.into_owned())?; // Now expand. - expansion::basic_expand_str(self, &formatted_prompt).await + let params = self.default_exec_params(); + expansion::basic_expand_str(self, ¶ms, &formatted_prompt).await } /// Returns the exit status of the last command executed in this shell. diff --git a/brush-core/src/tests.rs b/brush-core/src/tests.rs index ec2f9ad9..adf5adad 100644 --- a/brush-core/src/tests.rs +++ b/brush-core/src/tests.rs @@ -1,22 +1,23 @@ -use crate::{error, extendedtests, Shell}; +use crate::{error, extendedtests, ExecutionParameters, Shell}; pub(crate) fn eval_test_expr( expr: &brush_parser::ast::TestExpr, shell: &mut Shell, + params: &ExecutionParameters, ) -> Result { match expr { brush_parser::ast::TestExpr::False => Ok(false), brush_parser::ast::TestExpr::Literal(s) => Ok(!s.is_empty()), brush_parser::ast::TestExpr::And(left, right) => { - Ok(eval_test_expr(left, shell)? && eval_test_expr(right, shell)?) + Ok(eval_test_expr(left, shell, params)? && eval_test_expr(right, shell, params)?) } brush_parser::ast::TestExpr::Or(left, right) => { - Ok(eval_test_expr(left, shell)? || eval_test_expr(right, shell)?) + Ok(eval_test_expr(left, shell, params)? || eval_test_expr(right, shell, params)?) } - brush_parser::ast::TestExpr::Not(expr) => Ok(!eval_test_expr(expr, shell)?), - brush_parser::ast::TestExpr::Parenthesized(expr) => eval_test_expr(expr, shell), + brush_parser::ast::TestExpr::Not(expr) => Ok(!eval_test_expr(expr, shell, params)?), + brush_parser::ast::TestExpr::Parenthesized(expr) => eval_test_expr(expr, shell, params), brush_parser::ast::TestExpr::UnaryTest(op, operand) => { - extendedtests::apply_unary_predicate_to_str(op, operand, shell) + extendedtests::apply_unary_predicate_to_str(op, operand, shell, params) } brush_parser::ast::TestExpr::BinaryTest(op, left, right) => { extendedtests::apply_binary_predicate_to_strs(op, left.as_str(), right.as_str(), shell) diff --git a/brush-shell/tests/cases/redirection.yaml b/brush-shell/tests/cases/redirection.yaml index 5fa2d97d..89707c1e 100644 --- a/brush-shell/tests/cases/redirection.yaml +++ b/brush-shell/tests/cases/redirection.yaml @@ -101,13 +101,11 @@ cases: echo $(echo hi >&3) 3>output.txt - name: "Redirection in command substitution in braces to non-standard fd" - known_failure: true # https://github.com/reubeno/brush/issues/358 ignore_stderr: true stdin: | { : $(echo hi >&3); } 3>output.txt - name: "Redirection in command substitution in subshell to non-standard fd" - known_failure: true # https://github.com/reubeno/brush/issues/358 ignore_stderr: true stdin: | ( : $(echo hi >&3); ) 3>output.txt