1use std::borrow::Cow;
2use std::collections::VecDeque;
3use std::io::Write;
4use std::path::{Path, PathBuf};
5
6use itertools::Itertools;
7use shrub_parser::ast::{self, CommandPrefixOrSuffixItem};
8
9use crate::arithmetic::{self, ExpandAndEvaluate};
10use crate::commands::{self, CommandArg};
11use crate::env::{EnvironmentLookup, EnvironmentScope, valid_variable_name};
12use crate::openfiles::{OpenFile, OpenFiles};
13use crate::results::{
14 ExecutionExitCode, ExecutionResult, ExecutionSpawnResult, ExecutionWaitResult,
15};
16use crate::shell::Shell;
17use crate::variables::{
18 ArrayLiteral, ShellValue, ShellValueLiteral, ShellValueUnsetType, ShellVariable,
19};
20use crate::{
21 ShellFd, error, expansion, extendedtests, extensions, ioutils, jobs, openfiles, sys, timing,
22};
23
24struct PipelineExecutionContext<'a, SE: extensions::ShellExtensions> {
26 shell: commands::ShellForCommand<'a, SE>,
28 process_group_id: Option<i32>,
30}
31
32#[derive(Clone, Default)]
34pub struct ExecutionParameters {
35 open_files: openfiles::OpenFiles,
37 pub process_group_policy: ProcessGroupPolicy,
39 pub suppress_errexit: bool,
42}
43
44impl ExecutionParameters {
45 pub fn stdin(
51 &self,
52 shell: &Shell<impl extensions::ShellExtensions>,
53 ) -> impl std::io::Read + 'static {
54 self.try_stdin(shell).unwrap_or_else(|| {
55 ioutils::FailingReaderWriter::new("standard input not available").into()
56 })
57 }
58
59 pub fn try_stdin(&self, shell: &Shell<impl extensions::ShellExtensions>) -> Option<OpenFile> {
65 self.try_fd(shell, openfiles::OpenFiles::STDIN_FD)
66 }
67
68 pub fn stdout(
77 &self,
78 shell: &Shell<impl extensions::ShellExtensions>,
79 ) -> impl std::io::Write + 'static {
80 self.try_stdout(shell).unwrap_or_else(|| {
81 ioutils::FailingReaderWriter::new("standard output not available").into()
82 })
83 }
84
85 pub fn try_stdout(&self, shell: &Shell<impl extensions::ShellExtensions>) -> Option<OpenFile> {
91 self.try_fd(shell, openfiles::OpenFiles::STDOUT_FD)
92 }
93
94 pub fn stderr(
102 &self,
103 shell: &Shell<impl extensions::ShellExtensions>,
104 ) -> impl std::io::Write + 'static {
105 self.try_stderr(shell).unwrap_or_else(|| {
106 ioutils::FailingReaderWriter::new("standard error not available").into()
107 })
108 }
109
110 pub fn try_stderr(&self, shell: &Shell<impl extensions::ShellExtensions>) -> Option<OpenFile> {
116 self.try_fd(shell, openfiles::OpenFiles::STDERR_FD)
117 }
118
119 pub fn try_fd(
127 &self,
128 shell: &Shell<impl extensions::ShellExtensions>,
129 fd: ShellFd,
130 ) -> 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()
139 }
140 }
141 }
142
143 pub fn set_fd(&mut self, fd: ShellFd, file: openfiles::OpenFile) {
150 self.open_files.set_fd(fd, file);
151 }
152
153 pub fn iter_fds(
159 &self,
160 shell: &Shell<impl extensions::ShellExtensions>,
161 ) -> impl Iterator<Item = (ShellFd, openfiles::OpenFile)> {
162 let our_fds = self.open_files.iter_fds();
163 let shell_fds = shell
164 .persistent_open_files()
165 .iter_fds()
166 .filter(|(fd, _)| !self.open_files.contains_fd(*fd));
167
168 #[allow(clippy::needless_collect)]
169 let all_fds: Vec<_> = our_fds
170 .chain(shell_fds)
171 .map(|(fd, file)| (fd, file.clone()))
172 .collect();
173
174 all_fds.into_iter()
175 }
176}
177
178#[derive(Clone, Debug, Default)]
179pub enum ProcessGroupPolicy {
181 #[default]
183 NewProcessGroup,
184 SameProcessGroup,
186}
187
188#[async_trait::async_trait]
189pub trait Execute {
190 async fn execute(
191 &self,
192 shell: &mut Shell<impl extensions::ShellExtensions>,
193 params: &ExecutionParameters,
194 ) -> Result<ExecutionResult, error::Error>;
195}
196
197#[async_trait::async_trait]
198trait ExecuteInPipeline<SE: extensions::ShellExtensions> {
199 async fn execute_in_pipeline(
200 &self,
201 context: PipelineExecutionContext<'_, SE>,
202 params: ExecutionParameters,
203 ) -> Result<ExecutionSpawnResult, error::Error>;
204}
205
206#[async_trait::async_trait]
207impl Execute for ast::Program {
208 async fn execute(
209 &self,
210 shell: &mut Shell<impl extensions::ShellExtensions>,
211 params: &ExecutionParameters,
212 ) -> Result<ExecutionResult, error::Error> {
213 let mut result = ExecutionResult::success();
214
215 for command in &self.complete_commands {
216 match command.execute(shell, params).await {
221 Ok(exec_result) => result = exec_result,
222 Err(err) => {
223 let _ = shell.display_error(&mut params.stderr(shell), &err);
225 result = err.into_result(shell);
226 }
227 }
228
229 shell.set_last_exit_status(result.exit_code.into());
231
232 if !result.is_normal_flow() {
234 break;
235 }
236 }
237
238 Ok(result)
239 }
240}
241
242#[async_trait::async_trait]
243impl Execute for ast::CompoundList {
244 async fn execute(
245 &self,
246 shell: &mut Shell<impl extensions::ShellExtensions>,
247 params: &ExecutionParameters,
248 ) -> Result<ExecutionResult, error::Error> {
249 let mut result = ExecutionResult::success();
250
251 for ast::CompoundListItem(ao_list, sep) in &self.0 {
252 let run_async = matches!(sep, ast::SeparatorOperator::Async);
253
254 if run_async {
255 let job = spawn_async_ao_list_in_task(ao_list, shell, params);
256 let job_formatted = job.to_pid_style_string();
257
258 if shell.options().interactive && !shell.is_subshell() {
259 writeln!(params.stderr(shell), "{job_formatted}")?;
260 }
261
262 result = ExecutionResult::success();
263 } else {
264 result = ao_list.execute(shell, params).await?;
265
266 shell.set_last_exit_status(result.exit_code.into());
268 }
269
270 if !result.is_normal_flow() {
271 break;
272 }
273 }
274
275 Ok(result)
276 }
277}
278
279fn spawn_async_ao_list_in_task<'a, SE: extensions::ShellExtensions>(
280 ao_list: &ast::AndOrList,
281 shell: &'a mut Shell<SE>,
282 params: &ExecutionParameters,
283) -> &'a jobs::Job {
284 let mut cloned_shell = shell.clone();
286 let mut cloned_params = params.clone();
287 let cloned_ao_list = ao_list.clone();
288
289 cloned_shell.options_mut().interactive = false;
292
293 if let Ok(null) = openfiles::null() {
295 cloned_params.set_fd(openfiles::OpenFiles::STDIN_FD, null);
296 }
297
298 let join_handle = sys::tasks::spawn(async move {
299 cloned_ao_list
300 .execute(&mut cloned_shell, &cloned_params)
301 .await
302 });
303
304 shell.jobs_mut().add_as_current(jobs::Job::new(
305 [jobs::JobTask::Internal(join_handle)],
306 ao_list.to_string(),
307 jobs::JobState::Running,
308 ))
309}
310
311#[async_trait::async_trait]
312impl Execute for ast::AndOrList {
313 async fn execute(
314 &self,
315 shell: &mut Shell<impl extensions::ShellExtensions>,
316 params: &ExecutionParameters,
317 ) -> Result<ExecutionResult, error::Error> {
318 let has_operators = !self.additional.is_empty();
319
320 let mut first_params = params.clone();
323 if has_operators {
324 first_params.suppress_errexit = true;
325 }
326
327 let mut result = self.first.execute(shell, &first_params).await?;
328
329 for (index, next_ao) in self.additional.iter().enumerate() {
330 if !result.is_normal_flow() {
332 break;
333 }
334
335 let (is_and, pipeline) = match next_ao {
336 ast::AndOr::And(p) => (true, p),
337 ast::AndOr::Or(p) => (false, p),
338 };
339
340 if is_and {
345 if !result.is_success() {
346 continue;
347 }
348 } else if result.is_success() {
349 continue;
350 }
351
352 let mut params = params.clone();
356
357 let is_last = index == self.additional.len() - 1;
358 if !is_last {
359 params.suppress_errexit = true;
360 }
361
362 result = pipeline.execute(shell, ¶ms).await?;
363 }
364
365 Ok(result)
366 }
367}
368
369#[async_trait::async_trait]
370impl Execute for ast::Pipeline {
371 async fn execute(
372 &self,
373 shell: &mut Shell<impl extensions::ShellExtensions>,
374 params: &ExecutionParameters,
375 ) -> Result<ExecutionResult, error::Error> {
376 let stopwatch = self
378 .timed
379 .is_some()
380 .then(timing::start_timing)
381 .transpose()?;
382
383 let mut params = params.clone();
384
385 if self.bang {
387 params.suppress_errexit = true;
388 }
389
390 let spawn_results = spawn_pipeline_processes(self, shell, ¶ms).await?;
393
394 let mut result =
397 wait_for_pipeline_processes_and_update_status(self, spawn_results, shell, ¶ms)
398 .await?;
399
400 if self.bang {
402 result.exit_code = ExecutionExitCode::from(if result.is_success() { 1 } else { 0 });
403 }
404
405 shell.set_last_exit_status(result.exit_code.into());
407
408 if !result.is_success() && !params.suppress_errexit && !self.bang {
414 if shell.traps().handles(crate::traps::TrapSignal::Err) {
415 shell
416 .invoke_trap_handler(crate::traps::TrapSignal::Err, ¶ms)
417 .await?;
418 }
419 }
420
421 if !params.suppress_errexit && !self.bang {
423 shell.apply_errexit_if_enabled(&mut result);
424 }
425
426 if let (Some(timed), Some(stopwatch)) = (&self.timed, &stopwatch)
428 && let Some(mut stderr) = params.try_fd(shell, openfiles::OpenFiles::STDERR_FD)
429 {
430 let timing = stopwatch.stop()?;
431 if timed.is_posix_output() {
432 std::write!(
433 stderr,
434 "real {}\nuser {}\nsys {}\n",
435 timing::format_duration_posixly(&timing.wall),
436 timing::format_duration_posixly(&timing.user),
437 timing::format_duration_posixly(&timing.system),
438 )?;
439 } else {
440 std::write!(
441 stderr,
442 "\nreal\t{}\nuser\t{}\nsys\t{}\n",
443 timing::format_duration_non_posixly(&timing.wall),
444 timing::format_duration_non_posixly(&timing.user),
445 timing::format_duration_non_posixly(&timing.system),
446 )?;
447 }
448 }
449
450 Ok(result)
451 }
452}
453
454async fn spawn_pipeline_processes(
455 pipeline: &ast::Pipeline,
456 shell: &mut Shell<impl extensions::ShellExtensions>,
457 params: &ExecutionParameters,
458) -> Result<VecDeque<ExecutionSpawnResult>, error::Error> {
459 let pipeline_len = pipeline.seq.len();
460 let mut pipe_readers = vec![];
461 let mut pipe_writers = vec![];
462 let mut spawn_results = VecDeque::new();
463 let mut process_group_id: Option<i32> = None;
464
465 if pipeline_len > 1 {
468 pipe_readers.reserve_exact(pipeline_len - 1);
469 pipe_writers.reserve_exact(pipeline_len - 1);
470
471 for _ in 0..(pipeline_len - 1) {
472 let (reader, writer) = sys::pipes::pipe()?;
473 pipe_readers.push(Some(reader.into()));
474 pipe_writers.push(Some(writer.into()));
475 }
476 pipe_readers.push(None);
480 }
481
482 for (current_pipeline_index, command) in pipeline.seq.iter().enumerate() {
483 let run_in_current_shell = pipeline_len == 1
494 || (current_pipeline_index == pipeline_len - 1
495 && shell.options().run_last_pipeline_cmd_in_current_shell
496 && !shell.options().enable_job_control);
497
498 let mut cmd_params = params.clone();
500
501 if let Some(Some(reader)) = pipe_readers.pop() {
503 cmd_params.open_files.set_fd(OpenFiles::STDIN_FD, reader);
504 }
505 if let Some(Some(writer)) = pipe_writers.pop() {
506 cmd_params.open_files.set_fd(OpenFiles::STDOUT_FD, writer);
507 }
508
509 let pipeline_context = if !run_in_current_shell {
510 if current_pipeline_index > 0 {
513 cmd_params.process_group_policy = ProcessGroupPolicy::SameProcessGroup;
514 }
515
516 PipelineExecutionContext {
517 shell: commands::ShellForCommand::OwnedShell {
518 target: Box::new(shell.clone()),
519 parent: shell,
520 },
521 process_group_id,
522 }
523 } else {
524 PipelineExecutionContext {
525 shell: commands::ShellForCommand::ParentShell(shell),
526 process_group_id,
527 }
528 };
529
530 let spawn_result = command
531 .execute_in_pipeline(pipeline_context, cmd_params)
532 .await?;
533
534 if let ExecutionSpawnResult::StartedProcess(child) = &spawn_result {
536 if process_group_id.is_none() {
537 process_group_id = child.pgid();
538 }
539 }
540
541 spawn_results.push_back(spawn_result);
542 }
543
544 Ok(spawn_results)
545}
546
547async fn wait_for_pipeline_processes_and_update_status(
548 pipeline: &ast::Pipeline,
549 mut process_spawn_results: VecDeque<ExecutionSpawnResult>,
550 shell: &mut Shell<impl extensions::ShellExtensions>,
551 params: &ExecutionParameters,
552) -> Result<ExecutionResult, error::Error> {
553 let mut result = ExecutionResult::success();
554 let mut stopped_children = vec![];
555 let mut last_failure_exit_code: Option<ExecutionExitCode> = None;
556
557 shell.last_pipeline_statuses_mut().clear();
559
560 while let Some(child) = process_spawn_results.pop_front() {
561 let wait_result = if !stopped_children.is_empty() {
562 child.poll().await?
563 } else {
564 child.wait().await?
565 };
566
567 match wait_result {
568 ExecutionWaitResult::Completed(current_result) => {
569 result = current_result;
570 shell.set_last_exit_status(result.exit_code.into());
571 shell
572 .last_pipeline_statuses_mut()
573 .push(result.exit_code.into());
574
575 if !result.is_success() {
577 last_failure_exit_code = Some(result.exit_code);
578 }
579 }
580 ExecutionWaitResult::Stopped(child) => {
581 result = ExecutionResult::stopped();
582 shell.set_last_exit_status(result.exit_code.into());
583 shell
584 .last_pipeline_statuses_mut()
585 .push(result.exit_code.into());
586
587 stopped_children.push(jobs::JobTask::External(child));
588 }
589 }
590 }
591
592 if shell.options().return_last_failure_from_pipeline {
594 if let Some(failure_exit_code) = last_failure_exit_code {
595 result.exit_code = failure_exit_code;
596 }
597 }
598
599 if shell.options().interactive {
600 sys::terminal::move_self_to_foreground()?;
601 }
602
603 if !stopped_children.is_empty() {
606 let job = shell.jobs_mut().add_as_current(jobs::Job::new(
607 stopped_children,
608 pipeline.to_string(),
609 jobs::JobState::Stopped,
610 ));
611
612 let formatted = job.to_string();
613
614 writeln!(params.stderr(shell), "\r{formatted}")?;
616 }
617
618 Ok(result)
619}
620
621#[async_trait::async_trait]
622impl<SE: extensions::ShellExtensions> ExecuteInPipeline<SE> for ast::Command {
623 async fn execute_in_pipeline(
624 &self,
625 mut pipeline_context: PipelineExecutionContext<'_, SE>,
626 mut params: ExecutionParameters,
627 ) -> Result<ExecutionSpawnResult, error::Error> {
628 if pipeline_context.shell.options().do_not_execute_commands {
629 return Ok(ExecutionSpawnResult::Completed(ExecutionResult::success()));
630 }
631
632 pipeline_context.shell.set_current_cmd(self);
635
636 match self {
637 Self::Simple(simple) => simple.execute_in_pipeline(pipeline_context, params).await,
638 Self::Compound(compound, redirects) => {
639 if let Some(redirects) = redirects {
641 for redirect in &redirects.0 {
642 setup_redirect(&mut pipeline_context.shell, &mut params, redirect).await?;
643 }
644 }
645
646 match pipeline_context.shell {
647 commands::ShellForCommand::OwnedShell { target, .. } => {
650 let (mut shell, compound) = (*target, compound.clone());
651 Ok(ExecutionSpawnResult::StartedTask(
652 sys::tasks::spawn_blocking(async move {
653 compound.execute(&mut shell, ¶ms).await
654 })
655 .await,
656 ))
657 }
658 commands::ShellForCommand::ParentShell(shell) => {
659 Ok(compound.execute(shell, ¶ms).await?.into())
660 }
661 }
662 }
663 Self::Function(func) => Ok(func
664 .execute(&mut pipeline_context.shell, ¶ms)
665 .await?
666 .into()),
667 }
668 }
669}
670
671enum WhileOrUntil {
672 While,
673 Until,
674}
675
676#[async_trait::async_trait]
677impl Execute for ast::CompoundCommand {
678 async fn execute(
679 &self,
680 shell: &mut Shell<impl extensions::ShellExtensions>,
681 params: &ExecutionParameters,
682 ) -> Result<ExecutionResult, error::Error> {
683 match self {
684 Self::BraceGroup(ast::BraceGroupCommand { list, .. }) => {
685 list.execute(shell, params).await
686 }
687 Self::Subshell(ast::SubshellCommand { list, .. }) => {
688 let mut subshell = shell.clone();
692
693 let subshell_result = match list.execute(&mut subshell, params).await {
696 Ok(result) => result,
697 Err(error) => {
698 let mut stderr = params.stderr(shell);
701 let _ = shell.display_error(&mut stderr, &error);
702
703 error.into_result(&subshell)
705 }
706 };
707
708 Ok(ExecutionResult::from(subshell_result.exit_code))
712 }
713 Self::ForClause(f) => f.execute(shell, params).await,
714 Self::CaseClause(c) => c.execute(shell, params).await,
715 Self::IfClause(i) => i.execute(shell, params).await,
716 Self::WhileClause(w) => (WhileOrUntil::While, w).execute(shell, params).await,
717 Self::UntilClause(u) => (WhileOrUntil::Until, u).execute(shell, params).await,
718 Self::Arithmetic(a) => a.execute(shell, params).await,
719 Self::ArithmeticForClause(a) => a.execute(shell, params).await,
720 Self::Coprocess(c) => c.execute(shell, params).await,
721 Self::ExtendedTest(e) => {
722 let result =
723 if extendedtests::eval_extended_test_expr(&e.expr, shell, params).await? {
724 0
725 } else {
726 1
727 };
728 Ok(ExecutionResult::new(result))
729 }
730 }
731 }
732}
733
734#[async_trait::async_trait]
735impl Execute for ast::CoprocessCommand {
736 async fn execute(
737 &self,
738 shell: &mut Shell<impl extensions::ShellExtensions>,
739 params: &ExecutionParameters,
740 ) -> Result<ExecutionResult, error::Error> {
741 if shell.options().do_not_execute_commands {
742 return Ok(ExecutionResult::success());
743 }
744
745 let name = self
748 .name
749 .as_ref()
750 .map_or(Cow::Borrowed("COPROC"), |w| Cow::Owned(w.to_string()));
751
752 if !valid_variable_name(&name) {
753 writeln!(
754 params.stderr(shell),
755 "coproc {name}: not a valid identifier"
756 )?;
757 return Ok(ExecutionExitCode::GeneralError.into());
758 }
759
760 shell.check_kept_descriptors(shell.open_files().count_above_stderr() + 2)?;
762
763 let (stdin_reader, stdin_writer) = sys::pipes::pipe()?;
765 let (stdout_reader, stdout_writer) = sys::pipes::pipe()?;
766
767 let stdout_fd = shell.open_files_mut().add(stdout_reader.into())?;
771 let stdin_fd = shell.open_files_mut().add(stdin_writer.into())?;
772
773 let mut child_shell = shell.clone();
775 child_shell.options_mut().interactive = false;
776
777 let mut child_params = params.clone();
779 child_params
780 .open_files
781 .set_fd(OpenFiles::STDIN_FD, stdin_reader.into());
782 child_params
783 .open_files
784 .set_fd(OpenFiles::STDOUT_FD, stdout_writer.into());
785
786 let body = self.body.clone();
787 let join_handle = sys::tasks::spawn(async move {
788 let pipeline_context = PipelineExecutionContext {
789 shell: commands::ShellForCommand::ParentShell(&mut child_shell),
790 process_group_id: None,
791 };
792 let spawn_result = body
793 .execute_in_pipeline(pipeline_context, child_params)
794 .await?;
795 match spawn_result.wait().await? {
796 ExecutionWaitResult::Completed(result) => Ok(result),
797 ExecutionWaitResult::Stopped(_) => Ok(ExecutionResult::stopped()),
798 }
799 });
800
801 let job = shell.jobs_mut().add_as_current(jobs::Job::new(
802 [jobs::JobTask::Internal(join_handle)],
803 format!("coproc {name}"),
804 jobs::JobState::Running,
805 ));
806 let job_id = job.id;
807
808 let arr_value = ShellValue::from(vec![stdout_fd.to_string(), stdin_fd.to_string()]);
810 shell
811 .env_mut()
812 .set_global(name.clone(), ShellVariable::new(arr_value))?;
813
814 let pid_name = format!("{name}_PID");
817 shell
818 .env_mut()
819 .set_global(pid_name, ShellVariable::new(job_id.to_string()))?;
820
821 Ok(ExecutionResult::success())
822 }
823}
824
825#[async_trait::async_trait]
826impl Execute for ast::ForClauseCommand {
827 async fn execute(
828 &self,
829 shell: &mut Shell<impl extensions::ShellExtensions>,
830 params: &ExecutionParameters,
831 ) -> Result<ExecutionResult, error::Error> {
832 let mut result = ExecutionResult::success();
833
834 let expanded_values = if let Some(unexpanded_values) = &self.values {
837 expand_words(shell, params, unexpanded_values).await?
838 } else {
839 shell.current_shell_args().to_vec()
841 };
842
843 for value in expanded_values {
844 if shell.options().print_commands_and_arguments {
845 if let Some(unexpanded_values) = &self.values {
846 shell
847 .trace_command(
848 params,
849 std::format!(
850 "for {} in {}",
851 self.variable_name,
852 unexpanded_values.iter().join(" ")
853 ),
854 )
855 .await;
856 } else {
857 shell
858 .trace_command(params, std::format!("for {}", self.variable_name))
859 .await;
860 }
861 }
862
863 shell.env_mut().update_or_add(
865 &self.variable_name,
866 ShellValueLiteral::Scalar(value),
867 |_| Ok(()),
868 EnvironmentLookup::Anywhere,
869 EnvironmentScope::Global,
870 )?;
871
872 result = self.body.list.execute(shell, params).await?;
873 if result.is_return_or_exit() {
874 break;
875 }
876
877 let is_break = result.is_break();
878
879 result.next_control_flow = result.next_control_flow.try_decrement_loop_levels();
880
881 if is_break || result.is_continue() {
882 break;
883 }
884 }
885
886 shell.set_last_exit_status(result.exit_code.into());
887 Ok(result)
888 }
889}
890
891#[async_trait::async_trait]
892impl Execute for ast::CaseClauseCommand {
893 async fn execute(
894 &self,
895 shell: &mut Shell<impl extensions::ShellExtensions>,
896 params: &ExecutionParameters,
897 ) -> Result<ExecutionResult, error::Error> {
898 if shell.options().print_commands_and_arguments {
901 shell
902 .trace_command(params, std::format!("case {} in", self.value))
903 .await;
904 }
905
906 let expanded_value = expansion::basic_expand_word(shell, params, &self.value).await?;
907 let mut result: ExecutionResult = ExecutionResult::success();
908 let mut force_execute_next_case = false;
909
910 for case in &self.cases {
911 if force_execute_next_case {
912 force_execute_next_case = false;
913 } else {
914 let mut matches = false;
915 for pattern in &case.patterns {
916 let expanded_pattern = expansion::basic_expand_pattern(shell, params, pattern)
917 .await?
918 .set_extended_globbing(shell.options().extended_globbing)
919 .set_case_insensitive(shell.options().case_insensitive_conditionals);
920
921 if expanded_pattern.exactly_matches(expanded_value.as_str())? {
922 matches = true;
923 break;
924 }
925 }
926
927 if !matches {
928 continue;
929 }
930 }
931
932 result = if let Some(case_cmd) = &case.cmd {
933 case_cmd.execute(shell, params).await?
934 } else {
935 ExecutionResult::success()
936 };
937
938 if !result.is_normal_flow() {
941 break;
942 }
943
944 match case.post_action {
945 ast::CaseItemPostAction::ExitCase => break,
946 ast::CaseItemPostAction::UnconditionallyExecuteNextCaseItem => {
947 force_execute_next_case = true;
948 }
949 ast::CaseItemPostAction::ContinueEvaluatingCases => (),
950 }
951 }
952
953 shell.set_last_exit_status(result.exit_code.into());
954
955 Ok(result)
956 }
957}
958
959#[async_trait::async_trait]
960impl Execute for ast::IfClauseCommand {
961 async fn execute(
962 &self,
963 shell: &mut Shell<impl extensions::ShellExtensions>,
964 params: &ExecutionParameters,
965 ) -> Result<ExecutionResult, error::Error> {
966 let mut condition_params = params.clone();
968 condition_params.suppress_errexit = true;
969 let condition = self.condition.execute(shell, &condition_params).await?;
970
971 if !condition.is_normal_flow() {
973 return Ok(condition);
974 }
975
976 if condition.is_success() {
977 return self.then.execute(shell, params).await;
978 }
979
980 if let Some(elses) = &self.elses {
981 for else_clause in elses {
982 match &else_clause.condition {
983 Some(else_condition) => {
984 let else_condition_result =
985 else_condition.execute(shell, &condition_params).await?;
986
987 if !else_condition_result.is_normal_flow() {
990 return Ok(else_condition_result);
991 }
992
993 if else_condition_result.is_success() {
994 return else_clause.body.execute(shell, params).await;
995 }
996 }
997 None => {
998 return else_clause.body.execute(shell, params).await;
999 }
1000 }
1001 }
1002 }
1003
1004 let result = ExecutionResult::success();
1007 shell.set_last_exit_status(result.exit_code.into());
1008
1009 Ok(result)
1010 }
1011}
1012
1013#[async_trait::async_trait]
1014impl Execute for (WhileOrUntil, &ast::WhileOrUntilClauseCommand) {
1015 async fn execute(
1016 &self,
1017 shell: &mut Shell<impl extensions::ShellExtensions>,
1018 params: &ExecutionParameters,
1019 ) -> Result<ExecutionResult, error::Error> {
1020 let is_while = match self.0 {
1021 WhileOrUntil::While => true,
1022 WhileOrUntil::Until => false,
1023 };
1024 let test_condition = &self.1.0;
1025 let body = &self.1.1;
1026
1027 let mut result = ExecutionResult::success();
1028
1029 let mut condition_params = params.clone();
1031 condition_params.suppress_errexit = true;
1032
1033 loop {
1034 let condition_result = test_condition.execute(shell, &condition_params).await?;
1035
1036 shell.set_last_exit_status(condition_result.exit_code.into());
1038
1039 if !condition_result.is_normal_flow() {
1040 result = condition_result;
1041
1042 result.next_control_flow = result.next_control_flow.try_decrement_loop_levels();
1046 break;
1047 }
1048
1049 if condition_result.is_success() != is_while {
1050 break;
1051 }
1052
1053 result = body.list.execute(shell, params).await?;
1054 if result.is_return_or_exit() {
1055 break;
1056 }
1057
1058 let is_break = result.is_break();
1059
1060 result.next_control_flow = result.next_control_flow.try_decrement_loop_levels();
1061
1062 if is_break || result.is_continue() {
1063 break;
1064 }
1065 }
1066
1067 shell.set_last_exit_status(result.exit_code.into());
1068 Ok(result)
1069 }
1070}
1071
1072#[async_trait::async_trait]
1073impl Execute for ast::ArithmeticCommand {
1074 async fn execute(
1075 &self,
1076 shell: &mut Shell<impl extensions::ShellExtensions>,
1077 params: &ExecutionParameters,
1078 ) -> Result<ExecutionResult, error::Error> {
1079 let value = self.expr.eval(shell, params, true).await?;
1080 let result = if value != 0 {
1081 ExecutionResult::success()
1082 } else {
1083 ExecutionResult::general_error()
1084 };
1085
1086 shell.set_last_exit_status(result.exit_code.into());
1087
1088 Ok(result)
1089 }
1090}
1091
1092#[async_trait::async_trait]
1093impl Execute for ast::ArithmeticForClauseCommand {
1094 async fn execute(
1095 &self,
1096 shell: &mut Shell<impl extensions::ShellExtensions>,
1097 params: &ExecutionParameters,
1098 ) -> Result<ExecutionResult, error::Error> {
1099 let mut result = ExecutionResult::success();
1100 if let Some(initializer) = &self.initializer {
1101 initializer.eval(shell, params, true).await?;
1102 }
1103
1104 loop {
1105 if let Some(condition) = &self.condition {
1106 if !condition.value.is_empty() && condition.eval(shell, params, true).await? == 0 {
1109 break;
1110 }
1111 }
1112
1113 result = self.body.list.execute(shell, params).await?;
1114 if result.is_return_or_exit() {
1115 break;
1116 }
1117
1118 let is_break = result.is_break();
1119
1120 result.next_control_flow = result.next_control_flow.try_decrement_loop_levels();
1121
1122 if is_break || result.is_continue() {
1123 break;
1124 }
1125
1126 if let Some(updater) = &self.updater {
1127 updater.eval(shell, params, true).await?;
1128 }
1129 }
1130
1131 shell.set_last_exit_status(result.exit_code.into());
1132 Ok(result)
1133 }
1134}
1135
1136#[async_trait::async_trait]
1137impl Execute for ast::FunctionDefinition {
1138 async fn execute(
1139 &self,
1140 shell: &mut Shell<impl extensions::ShellExtensions>,
1141 _params: &ExecutionParameters,
1142 ) -> Result<ExecutionResult, error::Error> {
1143 let func_name = self.fname.value.clone();
1144
1145 if shell.options().posix_mode
1147 && shell
1148 .builtins()
1149 .get(&func_name)
1150 .is_some_and(|r| r.special_builtin)
1151 {
1152 return Err(
1153 error::Error::from(error::ErrorKind::FunctionNameShadowsSpecialBuiltin {
1154 name: func_name,
1155 })
1156 .into_fatal(),
1157 );
1158 }
1159
1160 let source_info = shell
1163 .call_stack()
1164 .current_frame()
1165 .map_or_else(crate::SourceInfo::default, |frame| {
1166 frame.adjusted_source_info()
1167 });
1168 shell.define_func(func_name, self.clone(), &source_info);
1169
1170 let result = ExecutionResult::success();
1171 shell.set_last_exit_status(result.exit_code.into());
1172
1173 Ok(result)
1174 }
1175}
1176
1177#[async_trait::async_trait]
1178#[allow(clippy::too_many_lines)]
1179impl<SE: extensions::ShellExtensions> ExecuteInPipeline<SE> for ast::SimpleCommand {
1180 async fn execute_in_pipeline(
1181 &self,
1182 mut context: PipelineExecutionContext<'_, SE>,
1183 mut params: ExecutionParameters,
1184 ) -> Result<ExecutionSpawnResult, error::Error> {
1185 let prefix_iter = self.prefix.as_ref().map(|s| s.0.iter()).unwrap_or_default();
1186 let suffix_iter = self.suffix.as_ref().map(|s| s.0.iter()).unwrap_or_default();
1187 let cmd_name_items = self
1188 .word_or_name
1189 .as_ref()
1190 .map(|won| CommandPrefixOrSuffixItem::Word(won.clone()));
1191
1192 let mut assignments = vec![];
1193 let mut args: Vec<CommandArg> = vec![];
1194 let mut command_takes_assignments = false;
1195
1196 let status_change_count_before_expansion = context.shell.last_exit_status_change_count();
1199
1200 for item in prefix_iter.chain(cmd_name_items.iter()).chain(suffix_iter) {
1201 match item {
1202 CommandPrefixOrSuffixItem::IoRedirect(redirect) => {
1203 if let Err(e) = setup_redirect(&mut context.shell, &mut params, redirect).await
1204 {
1205 writeln!(params.stderr(&context.shell), "error: {e}")?;
1206 return Ok(ExecutionResult::general_error().into());
1207 }
1208 }
1209 CommandPrefixOrSuffixItem::ProcessSubstitution(kind, subshell_command) => {
1210 let (installed_fd_num, substitution_file) = setup_process_substitution(
1211 &context.shell,
1212 ¶ms,
1213 kind,
1214 subshell_command,
1215 )?;
1216
1217 params
1218 .open_files
1219 .set_fd(installed_fd_num, substitution_file);
1220
1221 args.push(CommandArg::String(std::format!(
1222 "/dev/fd/{installed_fd_num}"
1223 )));
1224 }
1225 CommandPrefixOrSuffixItem::AssignmentWord(assignment, word) => {
1226 if args.is_empty() {
1227 assignments.push(assignment);
1232 } else {
1233 if command_takes_assignments {
1234 let expanded =
1241 expand_assignment(&mut context.shell, ¶ms, assignment).await?;
1242 args.push(CommandArg::Assignment(expanded));
1243 } else {
1244 let mut next_args = expansion::full_expand_and_split_word(
1249 &mut context.shell,
1250 ¶ms,
1251 word,
1252 )
1253 .await?
1254 .into_iter()
1255 .map(CommandArg::String)
1256 .collect();
1257 args.append(&mut next_args);
1258 }
1259 }
1260 }
1261 CommandPrefixOrSuffixItem::Word(arg) => {
1262 let mut next_args =
1263 expansion::full_expand_and_split_word(&mut context.shell, ¶ms, arg)
1264 .await?;
1265
1266 if args.is_empty() {
1267 if let Some(cmd_name) = next_args.first() {
1268 if context.shell.options().expand_aliases
1272 && let Some(alias_value) =
1273 context.shell.aliases().get(cmd_name.as_str())
1274 {
1275 let alias_words = tokenize_alias_body(&context.shell, alias_value);
1285 let mut alias_pieces =
1286 expand_words(&mut context.shell, ¶ms, alias_words).await?;
1287
1288 next_args.remove(0);
1289 alias_pieces.append(&mut next_args);
1290
1291 next_args = alias_pieces;
1292 }
1293
1294 if let Some(first_arg) = next_args.first()
1300 && context
1301 .shell
1302 .builtins()
1303 .get(first_arg.as_str())
1304 .is_some_and(|r| !r.disabled && r.declaration_builtin)
1305 {
1306 command_takes_assignments = true;
1307 }
1308 }
1309 }
1310
1311 let mut next_args = next_args.into_iter().map(CommandArg::String).collect();
1312 args.append(&mut next_args);
1313 }
1314 }
1315 }
1316
1317 if let Some(CommandArg::String(cmd_name)) = args.first() {
1319 let cmd_name = cmd_name.clone();
1320 let mut stderr = params.stderr(&context.shell);
1321
1322 let (owned_shell, parent_shell) = match context.shell {
1323 commands::ShellForCommand::ParentShell(shell) => (None, shell),
1324 commands::ShellForCommand::OwnedShell { target, parent } => (Some(target), parent),
1325 };
1326
1327 let shell = if let Some(owned_shell) = owned_shell {
1328 commands::ShellForCommand::OwnedShell {
1329 target: owned_shell,
1330 parent: parent_shell,
1331 }
1332 } else {
1333 commands::ShellForCommand::ParentShell(parent_shell)
1334 };
1335
1336 let context = PipelineExecutionContext {
1337 shell,
1338 process_group_id: context.process_group_id,
1339 };
1340
1341 match execute_command(context, params, cmd_name, &assignments, args).await {
1342 Ok(result) => Ok(result),
1343 Err(err) => {
1344 let _ = parent_shell.display_error(&mut stderr, &err);
1345
1346 let result = err.into_result(parent_shell);
1347 Ok(result.into())
1348 }
1349 }
1350 } else {
1351 for assignment in assignments {
1353 apply_assignment(
1357 assignment,
1358 &mut context.shell,
1359 ¶ms,
1360 false,
1361 None,
1362 EnvironmentScope::Global,
1363 )
1364 .await?;
1365 }
1366
1367 context.shell.update_last_arg_variable(None);
1371
1372 if status_change_count_before_expansion == context.shell.last_exit_status_change_count()
1378 {
1379 context.shell.set_last_exit_status(0);
1380 }
1381
1382 Ok(ExecutionResult::new(context.shell.last_exit_status()).into())
1385 }
1386 }
1387}
1388
1389async fn execute_command(
1390 context: PipelineExecutionContext<'_, impl extensions::ShellExtensions>,
1391 params: ExecutionParameters,
1392 cmd_name: String,
1393 assignments: &[&ast::Assignment],
1394 args: Vec<CommandArg>,
1395) -> Result<ExecutionSpawnResult, error::Error> {
1396 let mut scoped;
1399 let mut shell = match context.shell {
1400 commands::ShellForCommand::ParentShell(shell) => {
1401 scoped = shell.enter_scope(EnvironmentScope::Command);
1404 commands::ShellForCommand::ParentShell(&mut scoped)
1405 }
1406 commands::ShellForCommand::OwnedShell { mut target, parent } => {
1407 target.env_mut().push_scope(EnvironmentScope::Command);
1409 commands::ShellForCommand::OwnedShell { target, parent }
1410 }
1411 };
1412
1413 for assignment in assignments {
1414 apply_assignment(
1416 assignment,
1417 &mut shell,
1418 ¶ms,
1419 true,
1420 Some(EnvironmentScope::Command),
1421 EnvironmentScope::Command,
1422 )
1423 .await?;
1424 }
1425
1426 if shell.options().print_commands_and_arguments {
1427 shell
1428 .trace_command(
1429 ¶ms,
1430 args.iter().map(|arg| arg.quote_for_tracing()).join(" "),
1431 )
1432 .await;
1433 }
1434
1435 let mut cmd = commands::SimpleCommand::new(shell, params, cmd_name, args);
1437 cmd.process_group_id = context.process_group_id;
1438
1439 let _ = commands::on_preexecute(&mut cmd).await;
1441
1442 cmd.execute().await
1445}
1446
1447fn tokenize_alias_body(
1455 shell: &Shell<impl extensions::ShellExtensions>,
1456 alias_value: &str,
1457) -> Vec<String> {
1458 let options = shell.parser_options().tokenizer_options();
1461
1462 shrub_parser::tokenize_str_with_options(alias_value, &options).map_or_else(
1465 |_| {
1466 alias_value
1467 .split_ascii_whitespace()
1468 .map(str::to_owned)
1469 .collect()
1470 },
1471 |tokens| tokens.iter().map(|t| t.to_str().to_owned()).collect(),
1472 )
1473}
1474
1475async fn expand_words(
1478 shell: &mut Shell<impl extensions::ShellExtensions>,
1479 params: &ExecutionParameters,
1480 words: impl IntoIterator<Item = impl AsRef<str>>,
1481) -> Result<Vec<String>, error::Error> {
1482 let mut fields = vec![];
1485 for word in words {
1486 fields.extend(expansion::full_expand_and_split_word(shell, params, word).await?);
1487 }
1488 Ok(fields)
1489}
1490
1491async fn expand_assignment(
1492 shell: &mut Shell<impl extensions::ShellExtensions>,
1493 params: &ExecutionParameters,
1494 assignment: &ast::Assignment,
1495) -> Result<ast::Assignment, error::Error> {
1496 let value = expand_assignment_value(shell, params, &assignment.value).await?;
1497 Ok(ast::Assignment {
1498 name: basic_expand_assignment_name(shell, params, &assignment.name).await?,
1499 value,
1500 append: assignment.append,
1501 loc: assignment.loc.clone(),
1502 })
1503}
1504
1505async fn basic_expand_assignment_name(
1506 shell: &mut Shell<impl extensions::ShellExtensions>,
1507 params: &ExecutionParameters,
1508 name: &ast::AssignmentName,
1509) -> Result<ast::AssignmentName, error::Error> {
1510 match name {
1511 ast::AssignmentName::VariableName(name) => {
1512 let expanded = expansion::basic_expand_word(shell, params, name).await?;
1513 Ok(ast::AssignmentName::VariableName(expanded))
1514 }
1515 ast::AssignmentName::ArrayElementName(name, index) => {
1516 let expanded_name = expansion::basic_expand_word(shell, params, name).await?;
1517 let expanded_index = expansion::basic_expand_word(shell, params, index).await?;
1518 Ok(ast::AssignmentName::ArrayElementName(
1519 expanded_name,
1520 expanded_index,
1521 ))
1522 }
1523 }
1524}
1525
1526async fn expand_assignment_value(
1527 shell: &mut Shell<impl extensions::ShellExtensions>,
1528 params: &ExecutionParameters,
1529 value: &ast::AssignmentValue,
1530) -> Result<ast::AssignmentValue, error::Error> {
1531 let expanded = match value {
1532 ast::AssignmentValue::Scalar(s) => {
1533 let expanded_word = expansion::basic_expand_assignment_word(shell, params, s).await?;
1534 ast::AssignmentValue::Scalar(ast::Word::from(expanded_word))
1535 }
1536 ast::AssignmentValue::Array(arr) => {
1537 let mut expanded_values = vec![];
1538 for (key, value) in arr {
1539 if let Some(k) = key {
1540 let expanded_key = expansion::basic_expand_assignment_word(shell, params, k)
1541 .await?
1542 .into();
1543 let expanded_value =
1544 expansion::basic_expand_assignment_word(shell, params, value)
1545 .await?
1546 .into();
1547 expanded_values.push((Some(expanded_key), expanded_value));
1548 } else {
1549 let split_expanded_value =
1552 expansion::full_expand_and_split_word(shell, params, value).await?;
1553 for expanded_value in split_expanded_value {
1554 expanded_values.push((None, expanded_value.into()));
1555 }
1556 }
1557 }
1558
1559 ast::AssignmentValue::Array(expanded_values)
1560 }
1561 };
1562
1563 Ok(expanded)
1564}
1565
1566#[expect(clippy::too_many_lines)]
1567async fn apply_assignment(
1568 assignment: &ast::Assignment,
1569 shell: &mut Shell<impl extensions::ShellExtensions>,
1570 params: &ExecutionParameters,
1571 mut export: bool,
1572 required_scope: Option<EnvironmentScope>,
1573 creation_scope: EnvironmentScope,
1574) -> Result<(), error::Error> {
1575 let mut array_index;
1578 let variable_name = match &assignment.name {
1579 ast::AssignmentName::VariableName(name) => {
1580 array_index = None;
1581 name
1582 }
1583 ast::AssignmentName::ArrayElementName(name, index) => {
1584 let expanded = expansion::basic_expand_word(shell, params, index).await?;
1585 array_index = Some(expanded);
1586 name
1587 }
1588 };
1589
1590 let new_value = match &assignment.value {
1592 ast::AssignmentValue::Scalar(unexpanded_value) => {
1593 let value =
1594 expansion::basic_expand_assignment_word(shell, params, unexpanded_value).await?;
1595 ShellValueLiteral::Scalar(value)
1596 }
1597 ast::AssignmentValue::Array(unexpanded_values) => {
1598 let mut elements = vec![];
1599 for (unexpanded_key, unexpanded_value) in unexpanded_values {
1600 let key = match unexpanded_key {
1601 Some(unexpanded_key) => Some(
1602 expansion::basic_expand_assignment_word(shell, params, unexpanded_key)
1603 .await?,
1604 ),
1605 None => None,
1606 };
1607
1608 if key.is_some() {
1609 let value =
1610 expansion::basic_expand_assignment_word(shell, params, unexpanded_value)
1611 .await?;
1612 elements.push((key, value));
1613 } else {
1614 let values =
1617 expansion::full_expand_and_split_word(shell, params, unexpanded_value)
1618 .await?;
1619 for value in values {
1620 elements.push((None, value));
1621 }
1622 }
1623 }
1624 ShellValueLiteral::Array(ArrayLiteral(elements))
1625 }
1626 };
1627
1628 if shell.options().print_commands_and_arguments {
1629 let op = if assignment.append { "+=" } else { "=" };
1630 shell
1631 .trace_command(params, std::format!("{}{op}{new_value}", assignment.name))
1632 .await;
1633 }
1634
1635 if let Some(idx) = &array_index {
1637 let will_be_indexed_array =
1642 if let Some((_, existing_value)) = shell.env().get(variable_name) {
1643 !matches!(
1644 existing_value.value(),
1645 ShellValue::AssociativeArray(_)
1646 | ShellValue::Unset(ShellValueUnsetType::AssociativeArray)
1647 )
1648 } else {
1649 true
1650 };
1651
1652 if will_be_indexed_array {
1653 array_index = Some(
1654 arithmetic::expand_and_eval(shell, params, idx.as_str(), false)
1655 .await?
1656 .to_string(),
1657 );
1658 }
1659 }
1660
1661 let export_variables_on_modification = shell.options().export_variables_on_modification;
1663
1664 if required_scope.is_some()
1667 && shell
1668 .env()
1669 .get(variable_name.as_str())
1670 .is_some_and(|(_, var)| var.is_readonly())
1671 {
1672 let err = error::Error::from(error::ErrorKind::ReadonlyVariable);
1673 let _ = shell.display_error(&mut params.stderr(shell), &err);
1674 return Ok(());
1675 }
1676
1677 let lookup = required_scope.map_or(
1682 EnvironmentLookup::Anywhere,
1683 EnvironmentLookup::OnlyInCurrent,
1684 );
1685 let existing_value = shell
1686 .env_mut()
1687 .get_mut_using_policy(variable_name.as_str(), lookup);
1688 if let Some(existing_value) = existing_value {
1689 if let Some(array_index) = array_index {
1690 match new_value {
1691 ShellValueLiteral::Scalar(s) => {
1692 existing_value.assign_at_index(array_index, s, assignment.append)?;
1693 }
1694 ShellValueLiteral::Array(_) => {
1695 return error::unimp("replacing an array item with an array");
1696 }
1697 }
1698 } else {
1699 if !export
1700 && export_variables_on_modification
1701 && !matches!(new_value, ShellValueLiteral::Array(_))
1702 {
1703 export = true;
1704 }
1705
1706 existing_value.assign(new_value, assignment.append)?;
1707 }
1708
1709 if export {
1710 existing_value.export();
1711 }
1712
1713 return Ok(());
1715 }
1716
1717 let new_value = if let Some(array_index) = array_index {
1719 match new_value {
1720 ShellValueLiteral::Scalar(s) => {
1721 ShellValue::indexed_array_from_literals(ArrayLiteral(vec![(Some(array_index), s)]))
1722 }
1723 ShellValueLiteral::Array(_) => {
1724 return error::unimp("cannot assign list to array member");
1725 }
1726 }
1727 } else {
1728 match new_value {
1729 ShellValueLiteral::Scalar(s) => {
1730 export = export || shell.options().export_variables_on_modification;
1731
1732 let s = match shell.env().get(variable_name.as_str()) {
1737 Some((_, visible)) if assignment.append => {
1738 let mut appended = visible.clone();
1739 appended.assign(ShellValueLiteral::Scalar(s), true)?;
1740 appended.value().to_cow_str(shell).into_owned()
1741 }
1742 _ => s,
1743 };
1744 ShellValue::String(s)
1745 }
1746 ShellValueLiteral::Array(values) => ShellValue::indexed_array_from_literals(values),
1747 }
1748 };
1749
1750 let mut new_var = ShellVariable::new(new_value);
1751
1752 if export {
1753 new_var.export();
1754 }
1755
1756 shell.env_mut().add(variable_name, new_var, creation_scope)
1757}
1758
1759#[expect(clippy::too_many_lines)]
1760pub(crate) async fn setup_redirect(
1761 shell: &mut Shell<impl extensions::ShellExtensions>,
1762 params: &'_ mut ExecutionParameters,
1763 redirect: &ast::IoRedirect,
1764) -> Result<(), error::Error> {
1765 match redirect {
1766 ast::IoRedirect::OutputAndError(f, append) => {
1767 let mut expanded_fields =
1768 expansion::full_expand_and_split_word(shell, params, f).await?;
1769 if expanded_fields.len() != 1 {
1770 return Err(error::ErrorKind::InvalidRedirection.into());
1771 }
1772
1773 let expanded_file_path = expanded_fields.remove(0);
1774 setup_redirect_output_and_error_to(shell, params, &expanded_file_path, *append)?;
1775 }
1776
1777 ast::IoRedirect::File(specified_fd_num, kind, target) => {
1778 match target {
1779 ast::IoFileRedirectTarget::Filename(f) => {
1780 let mut options = std::fs::File::options();
1781
1782 let mut expanded_fields =
1783 expansion::full_expand_and_split_word(shell, params, f).await?;
1784
1785 if expanded_fields.len() != 1 {
1786 return Err(error::ErrorKind::InvalidRedirection.into());
1787 }
1788
1789 let expanded_file_path: PathBuf =
1790 shell.absolute_path(Path::new(expanded_fields.remove(0).as_str()));
1791
1792 let default_fd_if_unspecified = get_default_fd_for_redirect_kind(kind);
1793 match kind {
1794 ast::IoFileRedirectKind::Read => {
1795 options.read(true);
1796 }
1797 ast::IoFileRedirectKind::Write => {
1798 if shell
1799 .options()
1800 .disallow_overwriting_regular_files_via_output_redirection
1801 {
1802 if !expanded_file_path.is_file() {
1806 options.create(true);
1807 } else {
1808 options.create_new(true);
1809 }
1810 options.write(true);
1811 } else {
1812 options.create(true);
1813 options.write(true);
1814 options.truncate(true);
1815 }
1816 }
1817 ast::IoFileRedirectKind::Append => {
1818 options.create(true);
1819 options.append(true);
1820 }
1821 ast::IoFileRedirectKind::ReadAndWrite => {
1822 options.create(true);
1823 options.read(true);
1824 options.write(true);
1825 }
1826 ast::IoFileRedirectKind::Clobber => {
1827 options.create(true);
1828 options.write(true);
1829 options.truncate(true);
1830 }
1831 ast::IoFileRedirectKind::DuplicateInput => {
1832 options.read(true);
1833 }
1834 ast::IoFileRedirectKind::DuplicateOutput => {
1835 options.create(true);
1836 options.write(true);
1837 }
1838 }
1839
1840 let fd_num = specified_fd_num.unwrap_or(default_fd_if_unspecified);
1841
1842 let opened_file = shell
1843 .open_file(&options, &expanded_file_path, params)
1844 .map_err(|err| {
1845 error::ErrorKind::RedirectionFailure(
1846 expanded_file_path.to_string_lossy().to_string(),
1847 err.to_string(),
1848 )
1849 })?;
1850
1851 params.open_files.set_fd(fd_num, opened_file);
1852 }
1853
1854 ast::IoFileRedirectTarget::Fd(fd) => {
1855 let default_fd_if_unspecified = match kind {
1856 ast::IoFileRedirectKind::DuplicateInput => 0,
1857 ast::IoFileRedirectKind::DuplicateOutput => 1,
1858 _ => {
1859 return error::unimp("unexpected redirect kind");
1860 }
1861 };
1862
1863 let fd_num = specified_fd_num.unwrap_or(default_fd_if_unspecified);
1864
1865 if let Some(target_file) = params.try_fd(shell, *fd) {
1866 params.open_files.set_fd(fd_num, target_file);
1867 } else {
1868 return Err(error::ErrorKind::BadFileDescriptor(*fd).into());
1869 }
1870 }
1871
1872 ast::IoFileRedirectTarget::Duplicate(word) => {
1873 let default_fd_if_unspecified = match kind {
1874 ast::IoFileRedirectKind::DuplicateInput => 0,
1875 ast::IoFileRedirectKind::DuplicateOutput => 1,
1876 _ => {
1877 return error::unimp("unexpected redirect kind");
1878 }
1879 };
1880
1881 let fd_num = specified_fd_num.unwrap_or(default_fd_if_unspecified);
1882
1883 let mut expanded_fields =
1884 expansion::full_expand_and_split_word(shell, params, word).await?;
1885
1886 if expanded_fields.len() != 1 {
1887 return Err(error::ErrorKind::InvalidRedirection.into());
1888 }
1889
1890 let mut expanded = expanded_fields.remove(0);
1891
1892 let dash = if expanded.ends_with('-') {
1893 expanded.pop();
1894 true
1895 } else {
1896 false
1897 };
1898
1899 if expanded.is_empty() {
1900 } else if expanded.chars().all(|c: char| c.is_ascii_digit()) {
1902 let source_fd_num = expanded
1903 .parse::<ShellFd>()
1904 .map_err(|_| error::ErrorKind::InvalidRedirection)?;
1905
1906 let Some(target_file) = params.try_fd(shell, source_fd_num) else {
1909 return Err(error::ErrorKind::BadFileDescriptor(source_fd_num).into());
1910 };
1911
1912 params.open_files.set_fd(fd_num, target_file);
1913 } else if fd_num == 1 && !dash {
1914 setup_redirect_output_and_error_to(
1918 shell, params, &expanded, false, )?;
1920 } else {
1921 return Err(error::ErrorKind::InvalidRedirection.into());
1922 }
1923
1924 if dash {
1925 params.open_files.remove_fd(fd_num);
1927 }
1928 }
1929
1930 ast::IoFileRedirectTarget::ProcessSubstitution(substitution_kind, subshell_cmd) => {
1931 match kind {
1932 ast::IoFileRedirectKind::Read
1933 | ast::IoFileRedirectKind::Write
1934 | ast::IoFileRedirectKind::Append
1935 | ast::IoFileRedirectKind::ReadAndWrite
1936 | ast::IoFileRedirectKind::Clobber => {
1937 let (substitution_fd, substitution_file) = setup_process_substitution(
1938 shell,
1939 params,
1940 substitution_kind,
1941 subshell_cmd,
1942 )?;
1943
1944 let target_file = substitution_file.clone();
1945 params.open_files.set_fd(substitution_fd, substitution_file);
1946
1947 let fd_num = specified_fd_num
1948 .unwrap_or_else(|| get_default_fd_for_redirect_kind(kind));
1949
1950 params.open_files.set_fd(fd_num, target_file);
1951 }
1952 _ => return error::unimp("invalid process substitution"),
1953 }
1954 }
1955 }
1956 }
1957
1958 ast::IoRedirect::HereDocument(fd_num, io_here) => {
1959 let fd_num = fd_num.unwrap_or(0);
1961
1962 let io_here_doc = if io_here.requires_expansion {
1964 expansion::basic_expand_heredoc_word(shell, params, &io_here.doc).await?
1965 } else {
1966 io_here.doc.flatten()
1967 };
1968
1969 let f = setup_open_file_with_contents(io_here_doc.as_str())?;
1970
1971 params.open_files.set_fd(fd_num, f);
1972 }
1973
1974 ast::IoRedirect::HereString(fd_num, word) => {
1975 let fd_num = fd_num.unwrap_or(0);
1977
1978 let mut expanded_word = expansion::basic_expand_word(shell, params, word).await?;
1979 expanded_word.push('\n');
1980
1981 let f = setup_open_file_with_contents(expanded_word.as_str())?;
1982
1983 params.open_files.set_fd(fd_num, f);
1984 }
1985 }
1986
1987 Ok(())
1988}
1989
1990fn setup_redirect_output_and_error_to(
2000 shell: &Shell<impl extensions::ShellExtensions>,
2001 params: &mut ExecutionParameters,
2002 file_path: &str,
2003 append: bool,
2004) -> Result<(), error::Error> {
2005 let abs_file_path: PathBuf = shell.absolute_path(Path::new(file_path));
2006
2007 let mut file_options = std::fs::File::options();
2008 file_options
2009 .create(true)
2010 .write(true)
2011 .truncate(!append)
2012 .append(append);
2013
2014 let stdout_file = shell
2015 .open_file(&file_options, &abs_file_path, params)
2016 .map_err(|err| {
2017 error::ErrorKind::RedirectionFailure(
2018 abs_file_path.to_string_lossy().to_string(),
2019 err.to_string(),
2020 )
2021 })?;
2022
2023 let stderr_file = stdout_file.clone();
2024
2025 params.open_files.set_fd(OpenFiles::STDOUT_FD, stdout_file);
2026 params.open_files.set_fd(OpenFiles::STDERR_FD, stderr_file);
2027
2028 Ok(())
2029}
2030
2031const fn get_default_fd_for_redirect_kind(kind: &ast::IoFileRedirectKind) -> ShellFd {
2032 match kind {
2033 ast::IoFileRedirectKind::Read => 0,
2034 ast::IoFileRedirectKind::Write => 1,
2035 ast::IoFileRedirectKind::Append => 1,
2036 ast::IoFileRedirectKind::ReadAndWrite => 0,
2037 ast::IoFileRedirectKind::Clobber => 1,
2038 ast::IoFileRedirectKind::DuplicateInput => 0,
2039 ast::IoFileRedirectKind::DuplicateOutput => 1,
2040 }
2041}
2042
2043fn setup_process_substitution(
2044 shell: &Shell<impl extensions::ShellExtensions>,
2045 params: &ExecutionParameters,
2046 kind: &ast::ProcessSubstitutionKind,
2047 subshell_cmd: &ast::SubshellCommand,
2048) -> Result<(ShellFd, OpenFile), error::Error> {
2049 let mut subshell = shell.clone();
2052
2053 let mut child_params = params.clone();
2055 child_params.process_group_policy = ProcessGroupPolicy::SameProcessGroup;
2056
2057 let (reader, writer) = sys::pipes::pipe()?;
2059 let (reader, writer) = (reader.into(), writer.into());
2060
2061 let target_file = match kind {
2062 ast::ProcessSubstitutionKind::Read => {
2063 child_params.open_files.set_fd(OpenFiles::STDOUT_FD, writer);
2064 reader
2065 }
2066 ast::ProcessSubstitutionKind::Write => {
2067 child_params.open_files.set_fd(OpenFiles::STDIN_FD, reader);
2068 writer
2069 }
2070 };
2071
2072 let subshell_cmd = subshell_cmd.to_owned();
2075 sys::tasks::spawn(async move {
2076 let _ = subshell_cmd
2078 .list
2079 .execute(&mut subshell, &child_params)
2080 .await;
2081 });
2082
2083 let mut candidate_fd_num = 63;
2086 while params.open_files.contains_fd(candidate_fd_num) {
2087 candidate_fd_num -= 1;
2088 if candidate_fd_num == 0 {
2089 return error::unimp("no available file descriptors");
2090 }
2091 }
2092
2093 Ok((candidate_fd_num, target_file))
2094}
2095
2096fn setup_open_file_with_contents(contents: &str) -> Result<OpenFile, error::Error> {
2097 let (reader, mut writer) = sys::pipes::pipe()?;
2098
2099 let bytes = contents.as_bytes();
2100
2101 #[cfg(any(target_os = "linux", target_os = "android"))]
2103 {
2104 use std::os::fd::AsFd as _;
2105
2106 let len = i32::try_from(bytes.len())
2107 .map_err(|_err| error::Error::from(error::ErrorKind::TooMuchData))?;
2108 nix::fcntl::fcntl(reader.as_fd(), nix::fcntl::FcntlArg::F_SETPIPE_SZ(len))?;
2109 }
2110
2111 writer.write_all(bytes)?;
2112 drop(writer);
2113
2114 Ok(reader.into())
2115}
2116
2117#[cfg(test)]
2120#[cfg(target_os = "linux")] #[allow(clippy::panic_in_result_fn, reason = "assertions in a fallible test")]
2122mod tests {
2123 use super::*;
2124 use crate::{ProfileLoadBehavior, RcLoadBehavior};
2125
2126 #[tokio::test]
2132 async fn futures_for_a_simple_command_stay_small() -> anyhow::Result<()> {
2133 let mut shell = Shell::builder()
2134 .profile(ProfileLoadBehavior::Skip)
2135 .rc(RcLoadBehavior::Skip)
2136 .build()
2137 .await?;
2138 let params = shell.default_exec_params();
2139
2140 let program = shell.parse_string("true")?;
2141 let list = &program.complete_commands[0];
2142 let and_or = &list.0[0].0;
2143 let pipeline = &and_or.first;
2144 let command = &pipeline.seq[0];
2145 let ast::Command::Simple(simple) = command else {
2146 anyhow::bail!("expected a simple command");
2147 };
2148
2149 let mut sizes = vec![];
2150 let context = PipelineExecutionContext {
2151 shell: commands::ShellForCommand::ParentShell(&mut shell),
2152 process_group_id: None,
2153 };
2154 let future = simple.execute_in_pipeline(context, params.clone());
2155 sizes.push((
2156 "SimpleCommand::execute_in_pipeline",
2157 size_of_val(&*future),
2158 3072,
2159 ));
2160 drop(future);
2161
2162 let context = PipelineExecutionContext {
2163 shell: commands::ShellForCommand::ParentShell(&mut shell),
2164 process_group_id: None,
2165 };
2166 let future = command.execute_in_pipeline(context, params.clone());
2167 sizes.push(("Command::execute_in_pipeline", size_of_val(&*future), 768));
2168 drop(future);
2169
2170 let future = pipeline.execute(&mut shell, ¶ms);
2171 sizes.push(("Pipeline::execute", size_of_val(&*future), 768));
2172 drop(future);
2173
2174 let future = and_or.execute(&mut shell, ¶ms);
2175 sizes.push(("AndOrList::execute", size_of_val(&*future), 256));
2176 drop(future);
2177
2178 let future = list.execute(&mut shell, ¶ms);
2179 sizes.push(("CompoundList::execute", size_of_val(&*future), 128));
2180 drop(future);
2181
2182 for (name, size, limit) in sizes {
2183 assert!(
2184 size <= limit,
2185 "{name}'s future takes {size} bytes; the limit is {limit}"
2186 );
2187 }
2188
2189 Ok(())
2190 }
2191}