1use brush_parser::ast::{self, CommandPrefixOrSuffixItem};
2use itertools::Itertools;
3use std::collections::VecDeque;
4use std::io::Write;
5use std::path::{Path, PathBuf};
6use sys::commands::ExitStatusExt;
7
8use crate::arithmetic::{self, ExpandAndEvaluate};
9use crate::commands::{self, CommandArg};
10use crate::env::{EnvironmentLookup, EnvironmentScope};
11use crate::openfiles::{OpenFile, OpenFiles};
12use crate::results::{
13 self, ExecutionExitCode, ExecutionResult, ExecutionSpawnResult, ExecutionWaitResult,
14};
15use crate::shell::Shell;
16use crate::variables::{
17 ArrayLiteral, ShellValue, ShellValueLiteral, ShellValueUnsetType, ShellVariable,
18};
19use crate::{ShellFd, error, expansion, extendedtests, jobs, openfiles, processes, sys, timing};
20
21impl From<processes::ProcessWaitResult> for results::ExecutionResult {
22 fn from(wait_result: processes::ProcessWaitResult) -> Self {
23 match wait_result {
24 processes::ProcessWaitResult::Completed(output) => output.into(),
25 processes::ProcessWaitResult::Stopped => Self::stopped(),
26 }
27 }
28}
29
30impl From<std::process::Output> for results::ExecutionResult {
31 fn from(output: std::process::Output) -> Self {
32 if let Some(code) = output.status.code() {
33 #[expect(clippy::cast_sign_loss)]
34 return Self::new((code & 0xFF) as u8);
35 }
36
37 if let Some(signal) = output.status.signal() {
38 #[expect(clippy::cast_sign_loss)]
39 return Self::new((signal & 0xFF) as u8 + 128);
40 }
41
42 tracing::error!("unhandled process exit");
43 Self::new(127)
44 }
45}
46
47struct PipelineExecutionContext<'a> {
49 shell: &'a mut Shell,
51
52 current_pipeline_index: usize,
53 pipeline_len: usize,
54 output_pipes: &'a mut Vec<std::io::PipeReader>,
55
56 process_group_id: Option<i32>,
57}
58
59#[derive(Clone, Default)]
61pub struct ExecutionParameters {
62 open_files: openfiles::OpenFiles,
64 pub process_group_policy: ProcessGroupPolicy,
66}
67
68impl ExecutionParameters {
69 pub fn stdin(&self, shell: &Shell) -> impl std::io::Read + 'static {
75 self.try_stdin(shell).unwrap()
76 }
77
78 pub fn try_stdin(&self, shell: &Shell) -> Option<OpenFile> {
84 self.try_fd(shell, openfiles::OpenFiles::STDIN_FD)
85 }
86
87 pub fn stdout(&self, shell: &Shell) -> impl std::io::Write + 'static {
93 self.try_stdout(shell).unwrap()
94 }
95
96 pub fn try_stdout(&self, shell: &Shell) -> Option<OpenFile> {
102 self.try_fd(shell, openfiles::OpenFiles::STDOUT_FD)
103 }
104
105 pub fn stderr(&self, shell: &Shell) -> impl std::io::Write + 'static {
111 self.try_stderr(shell).unwrap()
112 }
113
114 pub fn try_stderr(&self, shell: &Shell) -> Option<OpenFile> {
120 self.try_fd(shell, openfiles::OpenFiles::STDERR_FD)
121 }
122
123 pub fn try_fd(&self, shell: &Shell, fd: ShellFd) -> Option<openfiles::OpenFile> {
131 match self.open_files.fd_entry(fd) {
132 openfiles::OpenFileEntry::Open(f) => Some(f.clone()),
133 openfiles::OpenFileEntry::NotPresent => None,
134 openfiles::OpenFileEntry::NotSpecified => {
135 shell.persistent_open_files().try_fd(fd).cloned()
138 }
139 }
140 }
141
142 pub fn set_fd(&mut self, fd: ShellFd, file: openfiles::OpenFile) {
149 self.open_files.set_fd(fd, file);
150 }
151
152 pub fn iter_fds(&self, shell: &Shell) -> impl Iterator<Item = (ShellFd, openfiles::OpenFile)> {
158 let our_fds = self.open_files.iter_fds();
159 let shell_fds = shell
160 .persistent_open_files()
161 .iter_fds()
162 .filter(|(fd, _)| !self.open_files.contains_fd(*fd));
163
164 #[allow(clippy::needless_collect)]
165 let all_fds: Vec<_> = our_fds
166 .chain(shell_fds)
167 .map(|(fd, file)| (fd, file.clone()))
168 .collect();
169
170 all_fds.into_iter()
171 }
172}
173
174#[derive(Clone, Debug, Default)]
175pub enum ProcessGroupPolicy {
177 #[default]
179 NewProcessGroup,
180 SameProcessGroup,
182}
183
184#[async_trait::async_trait]
185pub trait Execute {
186 async fn execute(
187 &self,
188 shell: &mut Shell,
189 params: &ExecutionParameters,
190 ) -> Result<ExecutionResult, error::Error>;
191}
192
193#[async_trait::async_trait]
194trait ExecuteInPipeline {
195 async fn execute_in_pipeline(
196 &self,
197 context: &mut PipelineExecutionContext<'_>,
198 params: ExecutionParameters,
199 ) -> Result<ExecutionSpawnResult, error::Error>;
200}
201
202#[async_trait::async_trait]
203impl Execute for ast::Program {
204 async fn execute(
205 &self,
206 shell: &mut Shell,
207 params: &ExecutionParameters,
208 ) -> Result<ExecutionResult, error::Error> {
209 let mut result = ExecutionResult::success();
210
211 for command in &self.complete_commands {
212 result = command.execute(shell, params).await?;
213 if !result.is_normal_flow() {
214 break;
215 }
216 }
217
218 *shell.last_exit_status_mut() = result.exit_code.into();
219 Ok(result)
220 }
221}
222
223#[async_trait::async_trait]
224impl Execute for ast::CompoundList {
225 async fn execute(
226 &self,
227 shell: &mut Shell,
228 params: &ExecutionParameters,
229 ) -> Result<ExecutionResult, error::Error> {
230 let mut result = ExecutionResult::success();
231
232 for ast::CompoundListItem(ao_list, sep) in &self.0 {
233 let run_async = matches!(sep, ast::SeparatorOperator::Async);
234
235 if run_async {
236 let job = spawn_ao_list_in_task(ao_list, shell, params);
240 let job_formatted = job.to_pid_style_string();
241
242 if shell.options.interactive && !shell.is_subshell() {
243 writeln!(params.stderr(shell), "{job_formatted}")?;
244 }
245
246 result = ExecutionResult::success();
247 } else {
248 result = ao_list.execute(shell, params).await?;
249 }
250
251 if !result.is_normal_flow() {
252 break;
253 }
254 }
255
256 *shell.last_exit_status_mut() = result.exit_code.into();
257 Ok(result)
258 }
259}
260
261fn spawn_ao_list_in_task<'a>(
262 ao_list: &ast::AndOrList,
263 shell: &'a mut Shell,
264 params: &ExecutionParameters,
265) -> &'a jobs::Job {
266 let mut cloned_shell = shell.clone();
268 let cloned_params = params.clone();
269 let cloned_ao_list = ao_list.clone();
270
271 cloned_shell.options.interactive = false;
273
274 let join_handle = tokio::spawn(async move {
275 cloned_ao_list
276 .execute(&mut cloned_shell, &cloned_params)
277 .await
278 });
279
280 shell.jobs.add_as_current(jobs::Job::new(
281 [jobs::JobTask::Internal(join_handle)],
282 ao_list.to_string(),
283 jobs::JobState::Running,
284 ))
285}
286
287#[async_trait::async_trait]
288impl Execute for ast::AndOrList {
289 async fn execute(
290 &self,
291 shell: &mut Shell,
292 params: &ExecutionParameters,
293 ) -> Result<ExecutionResult, error::Error> {
294 let mut result = self.first.execute(shell, params).await?;
295
296 for next_ao in &self.additional {
297 if !result.is_normal_flow() {
299 break;
300 }
301
302 let (is_and, pipeline) = match next_ao {
303 ast::AndOr::And(p) => (true, p),
304 ast::AndOr::Or(p) => (false, p),
305 };
306
307 if is_and {
312 if !result.is_success() {
313 continue;
314 }
315 } else if result.is_success() {
316 continue;
317 }
318
319 result = pipeline.execute(shell, params).await?;
320 }
321
322 Ok(result)
323 }
324}
325
326#[async_trait::async_trait]
327impl Execute for ast::Pipeline {
328 async fn execute(
329 &self,
330 shell: &mut Shell,
331 params: &ExecutionParameters,
332 ) -> Result<ExecutionResult, error::Error> {
333 let stopwatch = self
335 .timed
336 .is_some()
337 .then(timing::start_timing)
338 .transpose()?;
339
340 let spawn_results = spawn_pipeline_processes(self, shell, params).await?;
343
344 let mut result =
346 wait_for_pipeline_processes_and_update_status(self, spawn_results, shell, params)
347 .await?;
348
349 if self.bang {
351 result.exit_code = ExecutionExitCode::from(if result.is_success() { 1 } else { 0 });
352 }
353
354 *shell.last_exit_status_mut() = result.exit_code.into();
356
357 if let Some(timed) = &self.timed {
359 if let Some(mut stderr) = params.try_fd(shell, openfiles::OpenFiles::STDERR_FD) {
360 let timing = stopwatch.unwrap().stop()?;
361
362 if timed.is_posix_output() {
363 std::write!(
364 stderr,
365 "real {}\nuser {}\nsys {}\n",
366 timing::format_duration_posixly(&timing.wall),
367 timing::format_duration_posixly(&timing.user),
368 timing::format_duration_posixly(&timing.system),
369 )?;
370 } else {
371 std::write!(
372 stderr,
373 "\nreal\t{}\nuser\t{}\nsys\t{}\n",
374 timing::format_duration_non_posixly(&timing.wall),
375 timing::format_duration_non_posixly(&timing.user),
376 timing::format_duration_non_posixly(&timing.system),
377 )?;
378 }
379 }
380 }
381
382 Ok(result)
383 }
384}
385
386async fn spawn_pipeline_processes(
387 pipeline: &ast::Pipeline,
388 shell: &mut Shell,
389 params: &ExecutionParameters,
390) -> Result<VecDeque<ExecutionSpawnResult>, error::Error> {
391 let pipeline_len = pipeline.seq.len();
392 let mut output_pipes = vec![];
393 let mut spawn_results = VecDeque::new();
394 let mut process_group_id: Option<i32> = None;
395
396 for (current_pipeline_index, command) in pipeline.seq.iter().enumerate() {
397 let run_in_current_shell = pipeline_len == 1
406 || (current_pipeline_index == pipeline_len - 1
407 && shell.options.run_last_pipeline_cmd_in_current_shell
408 && !shell.options.enable_job_control);
409
410 if !run_in_current_shell {
411 let mut subshell = shell.clone();
412 let mut pipeline_context = PipelineExecutionContext {
413 shell: &mut subshell,
414 current_pipeline_index,
415 pipeline_len,
416 output_pipes: &mut output_pipes,
417 process_group_id,
418 };
419
420 let mut cmd_params = params.clone();
421
422 if current_pipeline_index > 0 {
424 cmd_params.process_group_policy = ProcessGroupPolicy::SameProcessGroup;
425 }
426
427 spawn_results.push_back(
428 command
429 .execute_in_pipeline(&mut pipeline_context, cmd_params)
430 .await?,
431 );
432 process_group_id = pipeline_context.process_group_id;
433 } else {
434 let mut pipeline_context = PipelineExecutionContext {
435 shell,
436 current_pipeline_index,
437 pipeline_len,
438 output_pipes: &mut output_pipes,
439 process_group_id,
440 };
441
442 spawn_results.push_back(
443 command
444 .execute_in_pipeline(&mut pipeline_context, params.clone())
445 .await?,
446 );
447 process_group_id = pipeline_context.process_group_id;
448 }
449 }
450
451 Ok(spawn_results)
452}
453
454async fn wait_for_pipeline_processes_and_update_status(
455 pipeline: &ast::Pipeline,
456 mut process_spawn_results: VecDeque<ExecutionSpawnResult>,
457 shell: &mut Shell,
458 params: &ExecutionParameters,
459) -> Result<ExecutionResult, error::Error> {
460 let mut result = ExecutionResult::success();
461 let mut stopped_children = vec![];
462
463 shell.last_pipeline_statuses.clear();
465
466 while let Some(child) = process_spawn_results.pop_front() {
467 match child.wait(!stopped_children.is_empty()).await? {
468 ExecutionWaitResult::Completed(current_result) => {
469 result = current_result;
470 *shell.last_exit_status_mut() = result.exit_code.into();
471 shell.last_pipeline_statuses.push(result.exit_code.into());
472 }
473 ExecutionWaitResult::Stopped(child) => {
474 result = ExecutionResult::stopped();
475 *shell.last_exit_status_mut() = result.exit_code.into();
476 shell.last_pipeline_statuses.push(result.exit_code.into());
477
478 stopped_children.push(jobs::JobTask::External(child));
479 }
480 }
481 }
482
483 if shell.options.interactive {
484 sys::terminal::move_self_to_foreground()?;
485 }
486
487 if !stopped_children.is_empty() {
490 let job = shell.jobs.add_as_current(jobs::Job::new(
491 stopped_children,
492 pipeline.to_string(),
493 jobs::JobState::Stopped,
494 ));
495
496 let formatted = job.to_string();
497
498 writeln!(params.stderr(shell), "\r{formatted}")?;
500 }
501
502 Ok(result)
503}
504
505#[async_trait::async_trait]
506impl ExecuteInPipeline for ast::Command {
507 async fn execute_in_pipeline(
508 &self,
509 pipeline_context: &mut PipelineExecutionContext<'_>,
510 mut params: ExecutionParameters,
511 ) -> Result<ExecutionSpawnResult, error::Error> {
512 if pipeline_context.shell.options.do_not_execute_commands {
513 return Ok(ExecutionSpawnResult::Completed(ExecutionResult::success()));
514 }
515
516 match self {
517 Self::Simple(simple) => simple.execute_in_pipeline(pipeline_context, params).await,
518 Self::Compound(compound, redirects) => {
519 setup_pipeline_redirection(&mut params.open_files, pipeline_context)?;
521
522 if let Some(redirects) = redirects {
524 for redirect in &redirects.0 {
525 setup_redirect(pipeline_context.shell, &mut params, redirect).await?;
526 }
527 }
528
529 Ok(compound
530 .execute(pipeline_context.shell, ¶ms)
531 .await?
532 .into())
533 }
534 Self::Function(func) => Ok(func.execute(pipeline_context.shell, ¶ms).await?.into()),
535 Self::ExtendedTest(e) => {
536 let result = if extendedtests::eval_extended_test_expr(
537 &e.expr,
538 pipeline_context.shell,
539 ¶ms,
540 )
541 .await?
542 {
543 0
544 } else {
545 1
546 };
547 Ok(ExecutionResult::new(result).into())
548 }
549 }
550 }
551}
552
553enum WhileOrUntil {
554 While,
555 Until,
556}
557
558#[async_trait::async_trait]
559impl Execute for ast::CompoundCommand {
560 async fn execute(
561 &self,
562 shell: &mut Shell,
563 params: &ExecutionParameters,
564 ) -> Result<ExecutionResult, error::Error> {
565 match self {
566 Self::BraceGroup(ast::BraceGroupCommand { list, .. }) => {
567 list.execute(shell, params).await
568 }
569 Self::Subshell(ast::SubshellCommand { list, .. }) => {
570 let mut subshell = shell.clone();
572 let subshell_result = list.execute(&mut subshell, params).await?;
573
574 Ok(ExecutionResult::from(subshell_result.exit_code))
577 }
578 Self::ForClause(f) => f.execute(shell, params).await,
579 Self::CaseClause(c) => c.execute(shell, params).await,
580 Self::IfClause(i) => i.execute(shell, params).await,
581 Self::WhileClause(w) => (WhileOrUntil::While, w).execute(shell, params).await,
582 Self::UntilClause(u) => (WhileOrUntil::Until, u).execute(shell, params).await,
583 Self::Arithmetic(a) => a.execute(shell, params).await,
584 Self::ArithmeticForClause(a) => a.execute(shell, params).await,
585 }
586 }
587}
588
589#[async_trait::async_trait]
590impl Execute for ast::ForClauseCommand {
591 async fn execute(
592 &self,
593 shell: &mut Shell,
594 params: &ExecutionParameters,
595 ) -> Result<ExecutionResult, error::Error> {
596 let mut result = ExecutionResult::success();
597
598 let mut expanded_values = vec![];
601 if let Some(unexpanded_values) = &self.values {
602 for value in unexpanded_values {
603 let mut expanded =
604 expansion::full_expand_and_split_word(shell, params, value).await?;
605 expanded_values.append(&mut expanded);
606 }
607 } else {
608 expanded_values.extend_from_slice(&shell.positional_parameters);
610 }
611
612 for value in expanded_values {
613 if shell.options.print_commands_and_arguments {
614 if let Some(unexpanded_values) = &self.values {
615 shell
616 .trace_command(
617 params,
618 std::format!(
619 "for {} in {}",
620 self.variable_name,
621 unexpanded_values.iter().join(" ")
622 ),
623 )
624 .await?;
625 } else {
626 shell
627 .trace_command(params, std::format!("for {}", self.variable_name,))
628 .await?;
629 }
630 }
631
632 shell.env.update_or_add(
634 &self.variable_name,
635 ShellValueLiteral::Scalar(value),
636 |_| Ok(()),
637 EnvironmentLookup::Anywhere,
638 EnvironmentScope::Global,
639 )?;
640
641 result = self.body.list.execute(shell, params).await?;
642 if result.is_return_or_exit() {
643 break;
644 }
645
646 let is_break = result.is_break();
647
648 result.next_control_flow = result.next_control_flow.try_decrement_loop_levels();
649
650 if is_break || result.is_continue() {
651 break;
652 }
653 }
654
655 *shell.last_exit_status_mut() = result.exit_code.into();
656 Ok(result)
657 }
658}
659
660#[async_trait::async_trait]
661impl Execute for ast::CaseClauseCommand {
662 async fn execute(
663 &self,
664 shell: &mut Shell,
665 params: &ExecutionParameters,
666 ) -> Result<ExecutionResult, error::Error> {
667 if shell.options.print_commands_and_arguments {
670 shell
671 .trace_command(params, std::format!("case {} in", &self.value))
672 .await?;
673 }
674
675 let expanded_value = expansion::basic_expand_word(shell, params, &self.value).await?;
676 let mut result: ExecutionResult = ExecutionResult::success();
677 let mut force_execute_next_case = false;
678
679 for case in &self.cases {
680 if force_execute_next_case {
681 force_execute_next_case = false;
682 } else {
683 let mut matches = false;
684 for pattern in &case.patterns {
685 let expanded_pattern = expansion::basic_expand_pattern(shell, params, pattern)
686 .await?
687 .set_extended_globbing(shell.options.extended_globbing)
688 .set_case_insensitive(shell.options.case_insensitive_conditionals);
689
690 if expanded_pattern.exactly_matches(expanded_value.as_str())? {
691 matches = true;
692 break;
693 }
694 }
695
696 if !matches {
697 continue;
698 }
699 }
700
701 result = if let Some(case_cmd) = &case.cmd {
702 case_cmd.execute(shell, params).await?
703 } else {
704 ExecutionResult::success()
705 };
706
707 if !result.is_normal_flow() {
709 break;
710 }
711
712 match case.post_action {
713 ast::CaseItemPostAction::ExitCase => break,
714 ast::CaseItemPostAction::UnconditionallyExecuteNextCaseItem => {
715 force_execute_next_case = true;
716 }
717 ast::CaseItemPostAction::ContinueEvaluatingCases => (),
718 }
719 }
720
721 *shell.last_exit_status_mut() = result.exit_code.into();
722
723 Ok(result)
724 }
725}
726
727#[async_trait::async_trait]
728impl Execute for ast::IfClauseCommand {
729 async fn execute(
730 &self,
731 shell: &mut Shell,
732 params: &ExecutionParameters,
733 ) -> Result<ExecutionResult, error::Error> {
734 let condition = self.condition.execute(shell, params).await?;
735
736 if !condition.is_normal_flow() {
738 return Ok(condition);
739 }
740
741 if condition.is_success() {
742 return self.then.execute(shell, params).await;
743 }
744
745 if let Some(elses) = &self.elses {
746 for else_clause in elses {
747 match &else_clause.condition {
748 Some(else_condition) => {
749 let else_condition_result = else_condition.execute(shell, params).await?;
750
751 if !else_condition_result.is_normal_flow() {
753 return Ok(else_condition_result);
754 }
755
756 if else_condition_result.is_success() {
757 return else_clause.body.execute(shell, params).await;
758 }
759 }
760 None => {
761 return else_clause.body.execute(shell, params).await;
762 }
763 }
764 }
765 }
766
767 let result = ExecutionResult::success();
768 *shell.last_exit_status_mut() = result.exit_code.into();
769
770 Ok(result)
771 }
772}
773
774#[async_trait::async_trait]
775impl Execute for (WhileOrUntil, &ast::WhileOrUntilClauseCommand) {
776 async fn execute(
777 &self,
778 shell: &mut Shell,
779 params: &ExecutionParameters,
780 ) -> Result<ExecutionResult, error::Error> {
781 let is_while = match self.0 {
782 WhileOrUntil::While => true,
783 WhileOrUntil::Until => false,
784 };
785 let test_condition = &self.1.0;
786 let body = &self.1.1;
787
788 let mut result = ExecutionResult::success();
789
790 loop {
791 let condition_result = test_condition.execute(shell, params).await?;
792 if !condition_result.is_normal_flow() {
793 result = condition_result;
794
795 result.next_control_flow = result.next_control_flow.try_decrement_loop_levels();
798 break;
799 }
800
801 if condition_result.is_success() != is_while {
802 break;
803 }
804
805 result = body.list.execute(shell, params).await?;
806 if result.is_return_or_exit() {
807 break;
808 }
809
810 let is_break = result.is_break();
811
812 result.next_control_flow = result.next_control_flow.try_decrement_loop_levels();
813
814 if is_break || result.is_continue() {
815 break;
816 }
817 }
818
819 Ok(result)
820 }
821}
822
823#[async_trait::async_trait]
824impl Execute for ast::ArithmeticCommand {
825 async fn execute(
826 &self,
827 shell: &mut Shell,
828 params: &ExecutionParameters,
829 ) -> Result<ExecutionResult, error::Error> {
830 let value = self.expr.eval(shell, params, true).await?;
831 let result = if value != 0 {
832 ExecutionResult::success()
833 } else {
834 ExecutionResult::general_error()
835 };
836
837 *shell.last_exit_status_mut() = result.exit_code.into();
838
839 Ok(result)
840 }
841}
842
843#[async_trait::async_trait]
844impl Execute for ast::ArithmeticForClauseCommand {
845 async fn execute(
846 &self,
847 shell: &mut Shell,
848 params: &ExecutionParameters,
849 ) -> Result<ExecutionResult, error::Error> {
850 let mut result = ExecutionResult::success();
851 if let Some(initializer) = &self.initializer {
852 initializer.eval(shell, params, true).await?;
853 }
854
855 loop {
856 if let Some(condition) = &self.condition {
857 if condition.eval(shell, params, true).await? == 0 {
858 break;
859 }
860 }
861
862 result = self.body.list.execute(shell, params).await?;
863 if result.is_return_or_exit() {
864 break;
865 }
866
867 let is_break = result.is_break();
868
869 result.next_control_flow = result.next_control_flow.try_decrement_loop_levels();
870
871 if is_break || result.is_continue() {
872 break;
873 }
874
875 if let Some(updater) = &self.updater {
876 updater.eval(shell, params, true).await?;
877 }
878 }
879
880 *shell.last_exit_status_mut() = result.exit_code.into();
881 Ok(result)
882 }
883}
884
885#[async_trait::async_trait]
886impl Execute for ast::FunctionDefinition {
887 async fn execute(
888 &self,
889 shell: &mut Shell,
890 _params: &ExecutionParameters,
891 ) -> Result<ExecutionResult, error::Error> {
892 shell.define_func(self.fname.value.clone(), self.clone());
893
894 let result = ExecutionResult::success();
895 *shell.last_exit_status_mut() = result.exit_code.into();
896
897 Ok(result)
898 }
899}
900
901#[async_trait::async_trait]
902#[allow(clippy::too_many_lines)]
903impl ExecuteInPipeline for ast::SimpleCommand {
904 async fn execute_in_pipeline(
905 &self,
906 context: &mut PipelineExecutionContext<'_>,
907 mut params: ExecutionParameters,
908 ) -> Result<ExecutionSpawnResult, error::Error> {
909 let prefix_iter = self.prefix.as_ref().map(|s| s.0.iter()).unwrap_or_default();
910 let suffix_iter = self.suffix.as_ref().map(|s| s.0.iter()).unwrap_or_default();
911 let cmd_name_items = self
912 .word_or_name
913 .as_ref()
914 .map(|won| CommandPrefixOrSuffixItem::Word(won.clone()));
915
916 setup_pipeline_redirection(&mut params.open_files, context)?;
918
919 let mut assignments = vec![];
920 let mut args: Vec<CommandArg> = vec![];
921 let mut command_takes_assignments = false;
922
923 for item in prefix_iter.chain(cmd_name_items.iter()).chain(suffix_iter) {
924 match item {
925 CommandPrefixOrSuffixItem::IoRedirect(redirect) => {
926 if let Err(e) = setup_redirect(context.shell, &mut params, redirect).await {
927 writeln!(params.stderr(context.shell), "error: {e}")?;
928 return Ok(ExecutionResult::general_error().into());
929 }
930 }
931 CommandPrefixOrSuffixItem::ProcessSubstitution(kind, subshell_command) => {
932 let (installed_fd_num, substitution_file) =
933 setup_process_substitution(context.shell, ¶ms, kind, subshell_command)?;
934
935 params
936 .open_files
937 .set_fd(installed_fd_num, substitution_file);
938
939 args.push(CommandArg::String(std::format!(
940 "/dev/fd/{installed_fd_num}"
941 )));
942 }
943 CommandPrefixOrSuffixItem::AssignmentWord(assignment, word) => {
944 if args.is_empty() {
945 assignments.push(assignment);
948 } else {
949 if command_takes_assignments {
950 let expanded =
954 expand_assignment(context.shell, ¶ms, assignment).await?;
955 args.push(CommandArg::Assignment(expanded));
956 } else {
957 let mut next_args =
961 expansion::full_expand_and_split_word(context.shell, ¶ms, word)
962 .await?
963 .into_iter()
964 .map(CommandArg::String)
965 .collect();
966 args.append(&mut next_args);
967 }
968 }
969 }
970 CommandPrefixOrSuffixItem::Word(arg) => {
971 let mut next_args =
972 expansion::full_expand_and_split_word(context.shell, ¶ms, arg).await?;
973
974 if args.is_empty() {
975 if let Some(cmd_name) = next_args.first() {
976 if let Some(alias_value) = context.shell.aliases.get(cmd_name.as_str())
977 {
978 let mut alias_pieces: Vec<_> = alias_value
983 .split_ascii_whitespace()
984 .map(|i| i.to_owned())
985 .collect();
986
987 next_args.remove(0);
988 alias_pieces.append(&mut next_args);
989
990 next_args = alias_pieces;
991 }
992
993 let first_arg = next_args[0].as_str();
994
995 if context
998 .shell
999 .builtins()
1000 .get(first_arg)
1001 .is_some_and(|r| !r.disabled && r.declaration_builtin)
1002 {
1003 command_takes_assignments = true;
1004 }
1005 }
1006 }
1007
1008 let mut next_args = next_args.into_iter().map(CommandArg::String).collect();
1009 args.append(&mut next_args);
1010 }
1011 }
1012 }
1013
1014 if let Some(CommandArg::String(cmd_name)) = args.first().cloned() {
1016 let mut stderr = params.stderr(context.shell);
1017
1018 match execute_command(context, params, cmd_name, assignments, args).await {
1019 Ok(result) => Ok(result),
1020 Err(err) => {
1021 let _ = context.shell.display_error(&mut stderr, &err).await;
1022 let exit_code = ExecutionExitCode::from(&err);
1023 Ok(ExecutionResult::from(exit_code).into())
1024 }
1025 }
1026 } else {
1027 *context.shell.last_exit_status_mut() = 0;
1029
1030 for assignment in assignments {
1032 apply_assignment(
1033 assignment,
1034 context.shell,
1035 ¶ms,
1036 false,
1037 None,
1038 EnvironmentScope::Global,
1039 )
1040 .await?;
1041 }
1042
1043 Ok(ExecutionResult::new(context.shell.last_result()).into())
1046 }
1047 }
1048}
1049
1050async fn execute_command(
1051 context: &mut PipelineExecutionContext<'_>,
1052 params: ExecutionParameters,
1053 cmd_name: String,
1054 assignments: Vec<&ast::Assignment>,
1055 args: Vec<CommandArg>,
1056) -> Result<ExecutionSpawnResult, error::Error> {
1057 context.shell.env.push_scope(EnvironmentScope::Command);
1061 for assignment in &assignments {
1062 apply_assignment(
1064 assignment,
1065 context.shell,
1066 ¶ms,
1067 true,
1068 Some(EnvironmentScope::Command),
1069 EnvironmentScope::Command,
1070 )
1071 .await?;
1072 }
1073
1074 if context.shell.options.print_commands_and_arguments {
1075 context
1076 .shell
1077 .trace_command(
1078 ¶ms,
1079 args.iter().map(|arg| arg.quote_for_tracing()).join(" "),
1080 )
1081 .await?;
1082 }
1083
1084 let mut cmd_context = commands::ExecutionContext {
1085 shell: context.shell,
1086 command_name: cmd_name,
1087 params,
1088 };
1089
1090 commands::on_preexecute(&mut cmd_context, args.as_slice()).await?;
1092
1093 let execution_result = commands::execute(
1095 cmd_context,
1096 &mut context.process_group_id,
1097 args,
1098 true, None,
1100 )
1101 .await;
1102
1103 context.shell.env.pop_scope(EnvironmentScope::Command)?;
1106
1107 execution_result
1108}
1109
1110async fn expand_assignment(
1111 shell: &mut Shell,
1112 params: &ExecutionParameters,
1113 assignment: &ast::Assignment,
1114) -> Result<ast::Assignment, error::Error> {
1115 let value = expand_assignment_value(shell, params, &assignment.value).await?;
1116 Ok(ast::Assignment {
1117 name: basic_expand_assignment_name(shell, params, &assignment.name).await?,
1118 value,
1119 append: assignment.append,
1120 loc: assignment.loc.clone(),
1121 })
1122}
1123
1124async fn basic_expand_assignment_name(
1125 shell: &mut Shell,
1126 params: &ExecutionParameters,
1127 name: &ast::AssignmentName,
1128) -> Result<ast::AssignmentName, error::Error> {
1129 match name {
1130 ast::AssignmentName::VariableName(name) => {
1131 let expanded = expansion::basic_expand_str(shell, params, name).await?;
1132 Ok(ast::AssignmentName::VariableName(expanded))
1133 }
1134 ast::AssignmentName::ArrayElementName(name, index) => {
1135 let expanded_name = expansion::basic_expand_str(shell, params, name).await?;
1136 let expanded_index = expansion::basic_expand_str(shell, params, index).await?;
1137 Ok(ast::AssignmentName::ArrayElementName(
1138 expanded_name,
1139 expanded_index,
1140 ))
1141 }
1142 }
1143}
1144
1145async fn expand_assignment_value(
1146 shell: &mut Shell,
1147 params: &ExecutionParameters,
1148 value: &ast::AssignmentValue,
1149) -> Result<ast::AssignmentValue, error::Error> {
1150 let expanded = match value {
1151 ast::AssignmentValue::Scalar(s) => {
1152 let expanded_word = expansion::basic_expand_word(shell, params, s).await?;
1153 ast::AssignmentValue::Scalar(ast::Word::from(expanded_word))
1154 }
1155 ast::AssignmentValue::Array(arr) => {
1156 let mut expanded_values = vec![];
1157 for (key, value) in arr {
1158 if let Some(k) = key {
1159 let expanded_key = expansion::basic_expand_word(shell, params, k).await?.into();
1160 let expanded_value = expansion::basic_expand_word(shell, params, value)
1161 .await?
1162 .into();
1163 expanded_values.push((Some(expanded_key), expanded_value));
1164 } else {
1165 let split_expanded_value =
1166 expansion::full_expand_and_split_word(shell, params, value).await?;
1167 for expanded_value in split_expanded_value {
1168 expanded_values.push((None, expanded_value.into()));
1169 }
1170 }
1171 }
1172
1173 ast::AssignmentValue::Array(expanded_values)
1174 }
1175 };
1176
1177 Ok(expanded)
1178}
1179
1180#[expect(clippy::too_many_lines)]
1181async fn apply_assignment(
1182 assignment: &ast::Assignment,
1183 shell: &mut Shell,
1184 params: &ExecutionParameters,
1185 mut export: bool,
1186 required_scope: Option<EnvironmentScope>,
1187 creation_scope: EnvironmentScope,
1188) -> Result<(), error::Error> {
1189 let mut array_index;
1192 let variable_name = match &assignment.name {
1193 ast::AssignmentName::VariableName(name) => {
1194 array_index = None;
1195 name
1196 }
1197 ast::AssignmentName::ArrayElementName(name, index) => {
1198 let expanded = expansion::basic_expand_str(shell, params, index).await?;
1199 array_index = Some(expanded);
1200 name
1201 }
1202 };
1203
1204 let new_value = match &assignment.value {
1206 ast::AssignmentValue::Scalar(unexpanded_value) => {
1207 let value = expansion::basic_expand_word(shell, params, unexpanded_value).await?;
1208 ShellValueLiteral::Scalar(value)
1209 }
1210 ast::AssignmentValue::Array(unexpanded_values) => {
1211 let mut elements = vec![];
1212 for (unexpanded_key, unexpanded_value) in unexpanded_values {
1213 let key = match unexpanded_key {
1214 Some(unexpanded_key) => {
1215 Some(expansion::basic_expand_word(shell, params, unexpanded_key).await?)
1216 }
1217 None => None,
1218 };
1219
1220 if key.is_some() {
1221 let value =
1222 expansion::basic_expand_word(shell, params, unexpanded_value).await?;
1223 elements.push((key, value));
1224 } else {
1225 let values =
1226 expansion::full_expand_and_split_word(shell, params, unexpanded_value)
1227 .await?;
1228 for value in values {
1229 elements.push((None, value));
1230 }
1231 }
1232 }
1233 ShellValueLiteral::Array(ArrayLiteral(elements))
1234 }
1235 };
1236
1237 if shell.options.print_commands_and_arguments {
1238 let op = if assignment.append { "+=" } else { "=" };
1239 shell
1240 .trace_command(params, std::format!("{}{op}{new_value}", assignment.name))
1241 .await?;
1242 }
1243
1244 if let Some(idx) = &array_index {
1246 let will_be_indexed_array = if let Some((_, existing_value)) = shell.env.get(variable_name)
1247 {
1248 matches!(
1249 existing_value.value(),
1250 ShellValue::IndexedArray(_) | ShellValue::Unset(ShellValueUnsetType::IndexedArray)
1251 )
1252 } else {
1253 true
1254 };
1255
1256 if will_be_indexed_array {
1257 array_index = Some(
1258 arithmetic::expand_and_eval(shell, params, idx.as_str(), false)
1259 .await?
1260 .to_string(),
1261 );
1262 }
1263 }
1264
1265 if let Some((existing_value_scope, existing_value)) = shell.env.get_mut(variable_name.as_str())
1267 {
1268 if required_scope.is_none() || Some(existing_value_scope) == required_scope {
1269 if let Some(array_index) = array_index {
1270 match new_value {
1271 ShellValueLiteral::Scalar(s) => {
1272 existing_value.assign_at_index(array_index, s, assignment.append)?;
1273 }
1274 ShellValueLiteral::Array(_) => {
1275 return error::unimp("replacing an array item with an array");
1276 }
1277 }
1278 } else {
1279 if !export
1280 && shell.options.export_variables_on_modification
1281 && !matches!(new_value, ShellValueLiteral::Array(_))
1282 {
1283 export = true;
1284 }
1285
1286 existing_value.assign(new_value, assignment.append)?;
1287 }
1288
1289 if export {
1290 existing_value.export();
1291 }
1292
1293 return Ok(());
1295 }
1296 }
1297
1298 let new_value = if let Some(array_index) = array_index {
1300 match new_value {
1301 ShellValueLiteral::Scalar(s) => {
1302 ShellValue::indexed_array_from_literals(ArrayLiteral(vec![(Some(array_index), s)]))
1303 }
1304 ShellValueLiteral::Array(_) => {
1305 return error::unimp("cannot assign list to array member");
1306 }
1307 }
1308 } else {
1309 match new_value {
1310 ShellValueLiteral::Scalar(s) => {
1311 export = export || shell.options.export_variables_on_modification;
1312 ShellValue::String(s)
1313 }
1314 ShellValueLiteral::Array(values) => ShellValue::indexed_array_from_literals(values),
1315 }
1316 };
1317
1318 let mut new_var = ShellVariable::new(new_value);
1319
1320 if export {
1321 new_var.export();
1322 }
1323
1324 shell.env.add(variable_name, new_var, creation_scope)
1325}
1326
1327fn setup_pipeline_redirection(
1328 open_files: &mut OpenFiles,
1329 context: &mut PipelineExecutionContext<'_>,
1330) -> Result<(), error::Error> {
1331 if context.current_pipeline_index > 0 {
1332 if let Some(preceding_output_reader) = context.output_pipes.pop() {
1334 open_files.set_fd(OpenFiles::STDIN_FD, preceding_output_reader.into());
1336 } else {
1337 open_files.set_fd(OpenFiles::STDIN_FD, openfiles::null()?);
1338 }
1339 }
1340
1341 if context.pipeline_len > 1 && context.current_pipeline_index < context.pipeline_len - 1 {
1344 let (reader, writer) = std::io::pipe()?;
1346 context.output_pipes.push(reader);
1347 open_files.set_fd(OpenFiles::STDOUT_FD, writer.into());
1348 }
1349
1350 Ok(())
1351}
1352
1353#[expect(clippy::too_many_lines)]
1354pub(crate) async fn setup_redirect(
1355 shell: &mut Shell,
1356 params: &'_ mut ExecutionParameters,
1357 redirect: &ast::IoRedirect,
1358) -> Result<(), error::Error> {
1359 match redirect {
1360 ast::IoRedirect::OutputAndError(f, append) => {
1361 let mut expanded_fields =
1362 expansion::full_expand_and_split_word(shell, params, f).await?;
1363 if expanded_fields.len() != 1 {
1364 return Err(error::ErrorKind::InvalidRedirection.into());
1365 }
1366
1367 let expanded_file_path: PathBuf =
1368 shell.absolute_path(Path::new(expanded_fields.remove(0).as_str()));
1369
1370 let mut file_options = std::fs::File::options();
1371 file_options
1372 .create(true)
1373 .write(true)
1374 .truncate(!*append)
1375 .append(*append);
1376
1377 let stdout_file = shell
1378 .open_file(&file_options, &expanded_file_path, params)
1379 .map_err(|err| {
1380 error::ErrorKind::RedirectionFailure(
1381 expanded_file_path.to_string_lossy().to_string(),
1382 err.to_string(),
1383 )
1384 })?;
1385
1386 let stderr_file = stdout_file.try_clone()?;
1387
1388 params.open_files.set_fd(OpenFiles::STDOUT_FD, stdout_file);
1389 params.open_files.set_fd(OpenFiles::STDERR_FD, stderr_file);
1390 }
1391
1392 ast::IoRedirect::File(specified_fd_num, kind, target) => {
1393 match target {
1394 ast::IoFileRedirectTarget::Filename(f) => {
1395 let mut options = std::fs::File::options();
1396
1397 let mut expanded_fields =
1398 expansion::full_expand_and_split_word(shell, params, f).await?;
1399
1400 if expanded_fields.len() != 1 {
1401 return Err(error::ErrorKind::InvalidRedirection.into());
1402 }
1403
1404 let expanded_file_path: PathBuf =
1405 shell.absolute_path(Path::new(expanded_fields.remove(0).as_str()));
1406
1407 let default_fd_if_unspecified = get_default_fd_for_redirect_kind(kind);
1408 match kind {
1409 ast::IoFileRedirectKind::Read => {
1410 options.read(true);
1411 }
1412 ast::IoFileRedirectKind::Write => {
1413 if shell
1414 .options
1415 .disallow_overwriting_regular_files_via_output_redirection
1416 {
1417 if !expanded_file_path.is_file() {
1420 options.create(true);
1421 } else {
1422 options.create_new(true);
1423 }
1424 options.write(true);
1425 } else {
1426 options.create(true);
1427 options.write(true);
1428 options.truncate(true);
1429 }
1430 }
1431 ast::IoFileRedirectKind::Append => {
1432 options.create(true);
1433 options.append(true);
1434 }
1435 ast::IoFileRedirectKind::ReadAndWrite => {
1436 options.create(true);
1437 options.read(true);
1438 options.write(true);
1439 }
1440 ast::IoFileRedirectKind::Clobber => {
1441 options.create(true);
1442 options.write(true);
1443 options.truncate(true);
1444 }
1445 ast::IoFileRedirectKind::DuplicateInput => {
1446 options.read(true);
1447 }
1448 ast::IoFileRedirectKind::DuplicateOutput => {
1449 options.create(true);
1450 options.write(true);
1451 }
1452 }
1453
1454 let fd_num = specified_fd_num.unwrap_or(default_fd_if_unspecified);
1455
1456 let opened_file = shell
1457 .open_file(&options, &expanded_file_path, params)
1458 .map_err(|err| {
1459 error::ErrorKind::RedirectionFailure(
1460 expanded_file_path.to_string_lossy().to_string(),
1461 err.to_string(),
1462 )
1463 })?;
1464
1465 params.open_files.set_fd(fd_num, opened_file);
1466 }
1467
1468 ast::IoFileRedirectTarget::Fd(fd) => {
1469 let default_fd_if_unspecified = match kind {
1470 ast::IoFileRedirectKind::DuplicateInput => 0,
1471 ast::IoFileRedirectKind::DuplicateOutput => 1,
1472 _ => {
1473 return error::unimp("unexpected redirect kind");
1474 }
1475 };
1476
1477 let fd_num = specified_fd_num.unwrap_or(default_fd_if_unspecified);
1478
1479 if let Some(f) = params.try_fd(shell, *fd) {
1480 let target_file = f.try_clone()?;
1481
1482 params.open_files.set_fd(fd_num, target_file);
1483 } else {
1484 return Err(error::ErrorKind::BadFileDescriptor(*fd).into());
1485 }
1486 }
1487
1488 ast::IoFileRedirectTarget::Duplicate(word) => {
1489 let default_fd_if_unspecified = match kind {
1490 ast::IoFileRedirectKind::DuplicateInput => 0,
1491 ast::IoFileRedirectKind::DuplicateOutput => 1,
1492 _ => {
1493 return error::unimp("unexpected redirect kind");
1494 }
1495 };
1496
1497 let fd_num = specified_fd_num.unwrap_or(default_fd_if_unspecified);
1498
1499 let mut expanded_fields =
1500 expansion::full_expand_and_split_word(shell, params, word).await?;
1501
1502 if expanded_fields.len() != 1 {
1503 return Err(error::ErrorKind::InvalidRedirection.into());
1504 }
1505
1506 let mut expanded = expanded_fields.remove(0);
1507
1508 let dash = if expanded.ends_with('-') {
1509 expanded.pop();
1510 true
1511 } else {
1512 false
1513 };
1514
1515 if expanded.is_empty() {
1516 } else if expanded.chars().all(|c: char| c.is_ascii_digit()) {
1518 let source_fd_num = expanded
1519 .parse::<ShellFd>()
1520 .map_err(|_| error::ErrorKind::InvalidRedirection)?;
1521
1522 let target_file = if let Some(f) = params.try_fd(shell, source_fd_num) {
1524 f.try_clone()?
1525 } else {
1526 return Err(error::ErrorKind::BadFileDescriptor(source_fd_num).into());
1527 };
1528
1529 params.open_files.set_fd(fd_num, target_file);
1530 } else {
1531 return Err(error::ErrorKind::InvalidRedirection.into());
1532 }
1533
1534 if dash {
1535 params.open_files.remove_fd(fd_num);
1537 }
1538 }
1539
1540 ast::IoFileRedirectTarget::ProcessSubstitution(substitution_kind, subshell_cmd) => {
1541 match kind {
1542 ast::IoFileRedirectKind::Read
1543 | ast::IoFileRedirectKind::Write
1544 | ast::IoFileRedirectKind::Append
1545 | ast::IoFileRedirectKind::ReadAndWrite
1546 | ast::IoFileRedirectKind::Clobber => {
1547 let (substitution_fd, substitution_file) = setup_process_substitution(
1548 shell,
1549 params,
1550 substitution_kind,
1551 subshell_cmd,
1552 )?;
1553
1554 let target_file = substitution_file.try_clone()?;
1555 params.open_files.set_fd(substitution_fd, substitution_file);
1556
1557 let fd_num = specified_fd_num
1558 .unwrap_or_else(|| get_default_fd_for_redirect_kind(kind));
1559
1560 params.open_files.set_fd(fd_num, target_file);
1561 }
1562 _ => return error::unimp("invalid process substitution"),
1563 }
1564 }
1565 }
1566 }
1567
1568 ast::IoRedirect::HereDocument(fd_num, io_here) => {
1569 let fd_num = fd_num.unwrap_or(0);
1571
1572 let io_here_doc = if io_here.requires_expansion {
1574 expansion::basic_expand_word(shell, params, &io_here.doc).await?
1575 } else {
1576 io_here.doc.flatten()
1577 };
1578
1579 let f = setup_open_file_with_contents(io_here_doc.as_str())?;
1580
1581 params.open_files.set_fd(fd_num, f);
1582 }
1583
1584 ast::IoRedirect::HereString(fd_num, word) => {
1585 let fd_num = fd_num.unwrap_or(0);
1587
1588 let mut expanded_word = expansion::basic_expand_word(shell, params, word).await?;
1589 expanded_word.push('\n');
1590
1591 let f = setup_open_file_with_contents(expanded_word.as_str())?;
1592
1593 params.open_files.set_fd(fd_num, f);
1594 }
1595 }
1596
1597 Ok(())
1598}
1599
1600const fn get_default_fd_for_redirect_kind(kind: &ast::IoFileRedirectKind) -> ShellFd {
1601 match kind {
1602 ast::IoFileRedirectKind::Read => 0,
1603 ast::IoFileRedirectKind::Write => 1,
1604 ast::IoFileRedirectKind::Append => 1,
1605 ast::IoFileRedirectKind::ReadAndWrite => 0,
1606 ast::IoFileRedirectKind::Clobber => 1,
1607 ast::IoFileRedirectKind::DuplicateInput => 0,
1608 ast::IoFileRedirectKind::DuplicateOutput => 1,
1609 }
1610}
1611
1612fn setup_process_substitution(
1613 shell: &Shell,
1614 params: &ExecutionParameters,
1615 kind: &ast::ProcessSubstitutionKind,
1616 subshell_cmd: &ast::SubshellCommand,
1617) -> Result<(ShellFd, OpenFile), error::Error> {
1618 let mut subshell = shell.clone();
1621
1622 let mut child_params = params.clone();
1624 child_params.process_group_policy = ProcessGroupPolicy::SameProcessGroup;
1625
1626 let (reader, writer) = std::io::pipe()?;
1628 let (reader, writer) = (reader.into(), writer.into());
1629
1630 let target_file = match kind {
1631 ast::ProcessSubstitutionKind::Read => {
1632 child_params.open_files.set_fd(OpenFiles::STDOUT_FD, writer);
1633 reader
1634 }
1635 ast::ProcessSubstitutionKind::Write => {
1636 child_params.open_files.set_fd(OpenFiles::STDIN_FD, reader);
1637 writer
1638 }
1639 };
1640
1641 let subshell_cmd = subshell_cmd.to_owned();
1644 tokio::spawn(async move {
1645 let _ = subshell_cmd
1647 .list
1648 .execute(&mut subshell, &child_params)
1649 .await;
1650 });
1651
1652 let mut candidate_fd_num = 63;
1655 while params.open_files.contains_fd(candidate_fd_num) {
1656 candidate_fd_num -= 1;
1657 if candidate_fd_num == 0 {
1658 return error::unimp("no available file descriptors");
1659 }
1660 }
1661
1662 Ok((candidate_fd_num, target_file))
1663}
1664
1665fn setup_open_file_with_contents(contents: &str) -> Result<OpenFile, error::Error> {
1666 let (reader, mut writer) = std::io::pipe()?;
1667
1668 let bytes = contents.as_bytes();
1669
1670 #[cfg(target_os = "linux")]
1671 {
1672 use std::os::fd::AsFd as _;
1673
1674 let len = i32::try_from(bytes.len())?;
1675 nix::fcntl::fcntl(reader.as_fd(), nix::fcntl::FcntlArg::F_SETPIPE_SZ(len))?;
1676 }
1677
1678 writer.write_all(bytes)?;
1679 drop(writer);
1680
1681 Ok(reader.into())
1682}