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 let (stdin_reader, stdin_writer) = sys::pipes::pipe()?;
762 let (stdout_reader, stdout_writer) = sys::pipes::pipe()?;
763
764 let stdout_fd = shell.open_files_mut().add(stdout_reader.into())?;
768 let stdin_fd = shell.open_files_mut().add(stdin_writer.into())?;
769
770 let mut child_shell = shell.clone();
772 child_shell.options_mut().interactive = false;
773
774 let mut child_params = params.clone();
776 child_params
777 .open_files
778 .set_fd(OpenFiles::STDIN_FD, stdin_reader.into());
779 child_params
780 .open_files
781 .set_fd(OpenFiles::STDOUT_FD, stdout_writer.into());
782
783 let body = self.body.clone();
784 let join_handle = sys::tasks::spawn(async move {
785 let pipeline_context = PipelineExecutionContext {
786 shell: commands::ShellForCommand::ParentShell(&mut child_shell),
787 process_group_id: None,
788 };
789 let spawn_result = body
790 .execute_in_pipeline(pipeline_context, child_params)
791 .await?;
792 match spawn_result.wait().await? {
793 ExecutionWaitResult::Completed(result) => Ok(result),
794 ExecutionWaitResult::Stopped(_) => Ok(ExecutionResult::stopped()),
795 }
796 });
797
798 let job = shell.jobs_mut().add_as_current(jobs::Job::new(
799 [jobs::JobTask::Internal(join_handle)],
800 format!("coproc {name}"),
801 jobs::JobState::Running,
802 ));
803 let job_id = job.id;
804
805 let arr_value = ShellValue::from(vec![stdout_fd.to_string(), stdin_fd.to_string()]);
807 shell
808 .env_mut()
809 .set_global(name.clone(), ShellVariable::new(arr_value))?;
810
811 let pid_name = format!("{name}_PID");
814 shell
815 .env_mut()
816 .set_global(pid_name, ShellVariable::new(job_id.to_string()))?;
817
818 Ok(ExecutionResult::success())
819 }
820}
821
822#[async_trait::async_trait]
823impl Execute for ast::ForClauseCommand {
824 async fn execute(
825 &self,
826 shell: &mut Shell<impl extensions::ShellExtensions>,
827 params: &ExecutionParameters,
828 ) -> Result<ExecutionResult, error::Error> {
829 let mut result = ExecutionResult::success();
830
831 let expanded_values = if let Some(unexpanded_values) = &self.values {
834 expand_words(shell, params, unexpanded_values).await?
835 } else {
836 shell.current_shell_args().to_vec()
838 };
839
840 for value in expanded_values {
841 if shell.options().print_commands_and_arguments {
842 if let Some(unexpanded_values) = &self.values {
843 shell
844 .trace_command(
845 params,
846 std::format!(
847 "for {} in {}",
848 self.variable_name,
849 unexpanded_values.iter().join(" ")
850 ),
851 )
852 .await;
853 } else {
854 shell
855 .trace_command(params, std::format!("for {}", self.variable_name))
856 .await;
857 }
858 }
859
860 shell.env_mut().update_or_add(
862 &self.variable_name,
863 ShellValueLiteral::Scalar(value),
864 |_| Ok(()),
865 EnvironmentLookup::Anywhere,
866 EnvironmentScope::Global,
867 )?;
868
869 result = self.body.list.execute(shell, params).await?;
870 if result.is_return_or_exit() {
871 break;
872 }
873
874 let is_break = result.is_break();
875
876 result.next_control_flow = result.next_control_flow.try_decrement_loop_levels();
877
878 if is_break || result.is_continue() {
879 break;
880 }
881 }
882
883 shell.set_last_exit_status(result.exit_code.into());
884 Ok(result)
885 }
886}
887
888#[async_trait::async_trait]
889impl Execute for ast::CaseClauseCommand {
890 async fn execute(
891 &self,
892 shell: &mut Shell<impl extensions::ShellExtensions>,
893 params: &ExecutionParameters,
894 ) -> Result<ExecutionResult, error::Error> {
895 if shell.options().print_commands_and_arguments {
898 shell
899 .trace_command(params, std::format!("case {} in", self.value))
900 .await;
901 }
902
903 let expanded_value = expansion::basic_expand_word(shell, params, &self.value).await?;
904 let mut result: ExecutionResult = ExecutionResult::success();
905 let mut force_execute_next_case = false;
906
907 for case in &self.cases {
908 if force_execute_next_case {
909 force_execute_next_case = false;
910 } else {
911 let mut matches = false;
912 for pattern in &case.patterns {
913 let expanded_pattern = expansion::basic_expand_pattern(shell, params, pattern)
914 .await?
915 .set_extended_globbing(shell.options().extended_globbing)
916 .set_case_insensitive(shell.options().case_insensitive_conditionals);
917
918 if expanded_pattern.exactly_matches(expanded_value.as_str())? {
919 matches = true;
920 break;
921 }
922 }
923
924 if !matches {
925 continue;
926 }
927 }
928
929 result = if let Some(case_cmd) = &case.cmd {
930 case_cmd.execute(shell, params).await?
931 } else {
932 ExecutionResult::success()
933 };
934
935 if !result.is_normal_flow() {
938 break;
939 }
940
941 match case.post_action {
942 ast::CaseItemPostAction::ExitCase => break,
943 ast::CaseItemPostAction::UnconditionallyExecuteNextCaseItem => {
944 force_execute_next_case = true;
945 }
946 ast::CaseItemPostAction::ContinueEvaluatingCases => (),
947 }
948 }
949
950 shell.set_last_exit_status(result.exit_code.into());
951
952 Ok(result)
953 }
954}
955
956#[async_trait::async_trait]
957impl Execute for ast::IfClauseCommand {
958 async fn execute(
959 &self,
960 shell: &mut Shell<impl extensions::ShellExtensions>,
961 params: &ExecutionParameters,
962 ) -> Result<ExecutionResult, error::Error> {
963 let mut condition_params = params.clone();
965 condition_params.suppress_errexit = true;
966 let condition = self.condition.execute(shell, &condition_params).await?;
967
968 if !condition.is_normal_flow() {
970 return Ok(condition);
971 }
972
973 if condition.is_success() {
974 return self.then.execute(shell, params).await;
975 }
976
977 if let Some(elses) = &self.elses {
978 for else_clause in elses {
979 match &else_clause.condition {
980 Some(else_condition) => {
981 let else_condition_result =
982 else_condition.execute(shell, &condition_params).await?;
983
984 if !else_condition_result.is_normal_flow() {
987 return Ok(else_condition_result);
988 }
989
990 if else_condition_result.is_success() {
991 return else_clause.body.execute(shell, params).await;
992 }
993 }
994 None => {
995 return else_clause.body.execute(shell, params).await;
996 }
997 }
998 }
999 }
1000
1001 let result = ExecutionResult::success();
1004 shell.set_last_exit_status(result.exit_code.into());
1005
1006 Ok(result)
1007 }
1008}
1009
1010#[async_trait::async_trait]
1011impl Execute for (WhileOrUntil, &ast::WhileOrUntilClauseCommand) {
1012 async fn execute(
1013 &self,
1014 shell: &mut Shell<impl extensions::ShellExtensions>,
1015 params: &ExecutionParameters,
1016 ) -> Result<ExecutionResult, error::Error> {
1017 let is_while = match self.0 {
1018 WhileOrUntil::While => true,
1019 WhileOrUntil::Until => false,
1020 };
1021 let test_condition = &self.1.0;
1022 let body = &self.1.1;
1023
1024 let mut result = ExecutionResult::success();
1025
1026 let mut condition_params = params.clone();
1028 condition_params.suppress_errexit = true;
1029
1030 loop {
1031 let condition_result = test_condition.execute(shell, &condition_params).await?;
1032
1033 shell.set_last_exit_status(condition_result.exit_code.into());
1035
1036 if !condition_result.is_normal_flow() {
1037 result = condition_result;
1038
1039 result.next_control_flow = result.next_control_flow.try_decrement_loop_levels();
1043 break;
1044 }
1045
1046 if condition_result.is_success() != is_while {
1047 break;
1048 }
1049
1050 result = body.list.execute(shell, params).await?;
1051 if result.is_return_or_exit() {
1052 break;
1053 }
1054
1055 let is_break = result.is_break();
1056
1057 result.next_control_flow = result.next_control_flow.try_decrement_loop_levels();
1058
1059 if is_break || result.is_continue() {
1060 break;
1061 }
1062 }
1063
1064 shell.set_last_exit_status(result.exit_code.into());
1065 Ok(result)
1066 }
1067}
1068
1069#[async_trait::async_trait]
1070impl Execute for ast::ArithmeticCommand {
1071 async fn execute(
1072 &self,
1073 shell: &mut Shell<impl extensions::ShellExtensions>,
1074 params: &ExecutionParameters,
1075 ) -> Result<ExecutionResult, error::Error> {
1076 let value = self.expr.eval(shell, params, true).await?;
1077 let result = if value != 0 {
1078 ExecutionResult::success()
1079 } else {
1080 ExecutionResult::general_error()
1081 };
1082
1083 shell.set_last_exit_status(result.exit_code.into());
1084
1085 Ok(result)
1086 }
1087}
1088
1089#[async_trait::async_trait]
1090impl Execute for ast::ArithmeticForClauseCommand {
1091 async fn execute(
1092 &self,
1093 shell: &mut Shell<impl extensions::ShellExtensions>,
1094 params: &ExecutionParameters,
1095 ) -> Result<ExecutionResult, error::Error> {
1096 let mut result = ExecutionResult::success();
1097 if let Some(initializer) = &self.initializer {
1098 initializer.eval(shell, params, true).await?;
1099 }
1100
1101 loop {
1102 if let Some(condition) = &self.condition {
1103 if !condition.value.is_empty() && condition.eval(shell, params, true).await? == 0 {
1106 break;
1107 }
1108 }
1109
1110 result = self.body.list.execute(shell, params).await?;
1111 if result.is_return_or_exit() {
1112 break;
1113 }
1114
1115 let is_break = result.is_break();
1116
1117 result.next_control_flow = result.next_control_flow.try_decrement_loop_levels();
1118
1119 if is_break || result.is_continue() {
1120 break;
1121 }
1122
1123 if let Some(updater) = &self.updater {
1124 updater.eval(shell, params, true).await?;
1125 }
1126 }
1127
1128 shell.set_last_exit_status(result.exit_code.into());
1129 Ok(result)
1130 }
1131}
1132
1133#[async_trait::async_trait]
1134impl Execute for ast::FunctionDefinition {
1135 async fn execute(
1136 &self,
1137 shell: &mut Shell<impl extensions::ShellExtensions>,
1138 _params: &ExecutionParameters,
1139 ) -> Result<ExecutionResult, error::Error> {
1140 let func_name = self.fname.value.clone();
1141
1142 if shell.options().posix_mode
1144 && shell
1145 .builtins()
1146 .get(&func_name)
1147 .is_some_and(|r| r.special_builtin)
1148 {
1149 return Err(
1150 error::Error::from(error::ErrorKind::FunctionNameShadowsSpecialBuiltin {
1151 name: func_name,
1152 })
1153 .into_fatal(),
1154 );
1155 }
1156
1157 let source_info = shell
1160 .call_stack()
1161 .current_frame()
1162 .map_or_else(crate::SourceInfo::default, |frame| {
1163 frame.adjusted_source_info()
1164 });
1165 shell.define_func(func_name, self.clone(), &source_info);
1166
1167 let result = ExecutionResult::success();
1168 shell.set_last_exit_status(result.exit_code.into());
1169
1170 Ok(result)
1171 }
1172}
1173
1174#[async_trait::async_trait]
1175#[allow(clippy::too_many_lines)]
1176impl<SE: extensions::ShellExtensions> ExecuteInPipeline<SE> for ast::SimpleCommand {
1177 async fn execute_in_pipeline(
1178 &self,
1179 mut context: PipelineExecutionContext<'_, SE>,
1180 mut params: ExecutionParameters,
1181 ) -> Result<ExecutionSpawnResult, error::Error> {
1182 let prefix_iter = self.prefix.as_ref().map(|s| s.0.iter()).unwrap_or_default();
1183 let suffix_iter = self.suffix.as_ref().map(|s| s.0.iter()).unwrap_or_default();
1184 let cmd_name_items = self
1185 .word_or_name
1186 .as_ref()
1187 .map(|won| CommandPrefixOrSuffixItem::Word(won.clone()));
1188
1189 let mut assignments = vec![];
1190 let mut args: Vec<CommandArg> = vec![];
1191 let mut command_takes_assignments = false;
1192
1193 let status_change_count_before_expansion = context.shell.last_exit_status_change_count();
1196
1197 for item in prefix_iter.chain(cmd_name_items.iter()).chain(suffix_iter) {
1198 match item {
1199 CommandPrefixOrSuffixItem::IoRedirect(redirect) => {
1200 if let Err(e) = setup_redirect(&mut context.shell, &mut params, redirect).await
1201 {
1202 writeln!(params.stderr(&context.shell), "error: {e}")?;
1203 return Ok(ExecutionResult::general_error().into());
1204 }
1205 }
1206 CommandPrefixOrSuffixItem::ProcessSubstitution(kind, subshell_command) => {
1207 let (installed_fd_num, substitution_file) = setup_process_substitution(
1208 &context.shell,
1209 ¶ms,
1210 kind,
1211 subshell_command,
1212 )?;
1213
1214 params
1215 .open_files
1216 .set_fd(installed_fd_num, substitution_file);
1217
1218 args.push(CommandArg::String(std::format!(
1219 "/dev/fd/{installed_fd_num}"
1220 )));
1221 }
1222 CommandPrefixOrSuffixItem::AssignmentWord(assignment, word) => {
1223 if args.is_empty() {
1224 assignments.push(assignment);
1229 } else {
1230 if command_takes_assignments {
1231 let expanded =
1238 expand_assignment(&mut context.shell, ¶ms, assignment).await?;
1239 args.push(CommandArg::Assignment(expanded));
1240 } else {
1241 let mut next_args = expansion::full_expand_and_split_word(
1246 &mut context.shell,
1247 ¶ms,
1248 word,
1249 )
1250 .await?
1251 .into_iter()
1252 .map(CommandArg::String)
1253 .collect();
1254 args.append(&mut next_args);
1255 }
1256 }
1257 }
1258 CommandPrefixOrSuffixItem::Word(arg) => {
1259 let mut next_args =
1260 expansion::full_expand_and_split_word(&mut context.shell, ¶ms, arg)
1261 .await?;
1262
1263 if args.is_empty() {
1264 if let Some(cmd_name) = next_args.first() {
1265 if context.shell.options().expand_aliases
1269 && let Some(alias_value) =
1270 context.shell.aliases().get(cmd_name.as_str())
1271 {
1272 let alias_words = tokenize_alias_body(&context.shell, alias_value);
1282 let mut alias_pieces =
1283 expand_words(&mut context.shell, ¶ms, alias_words).await?;
1284
1285 next_args.remove(0);
1286 alias_pieces.append(&mut next_args);
1287
1288 next_args = alias_pieces;
1289 }
1290
1291 if let Some(first_arg) = next_args.first()
1297 && context
1298 .shell
1299 .builtins()
1300 .get(first_arg.as_str())
1301 .is_some_and(|r| !r.disabled && r.declaration_builtin)
1302 {
1303 command_takes_assignments = true;
1304 }
1305 }
1306 }
1307
1308 let mut next_args = next_args.into_iter().map(CommandArg::String).collect();
1309 args.append(&mut next_args);
1310 }
1311 }
1312 }
1313
1314 if let Some(CommandArg::String(cmd_name)) = args.first() {
1316 let cmd_name = cmd_name.clone();
1317 let mut stderr = params.stderr(&context.shell);
1318
1319 let (owned_shell, parent_shell) = match context.shell {
1320 commands::ShellForCommand::ParentShell(shell) => (None, shell),
1321 commands::ShellForCommand::OwnedShell { target, parent } => (Some(target), parent),
1322 };
1323
1324 let shell = if let Some(owned_shell) = owned_shell {
1325 commands::ShellForCommand::OwnedShell {
1326 target: owned_shell,
1327 parent: parent_shell,
1328 }
1329 } else {
1330 commands::ShellForCommand::ParentShell(parent_shell)
1331 };
1332
1333 let context = PipelineExecutionContext {
1334 shell,
1335 process_group_id: context.process_group_id,
1336 };
1337
1338 match execute_command(context, params, cmd_name, &assignments, args).await {
1339 Ok(result) => Ok(result),
1340 Err(err) => {
1341 let _ = parent_shell.display_error(&mut stderr, &err);
1342
1343 let result = err.into_result(parent_shell);
1344 Ok(result.into())
1345 }
1346 }
1347 } else {
1348 for assignment in assignments {
1350 apply_assignment(
1354 assignment,
1355 &mut context.shell,
1356 ¶ms,
1357 false,
1358 None,
1359 EnvironmentScope::Global,
1360 )
1361 .await?;
1362 }
1363
1364 context.shell.update_last_arg_variable(None);
1368
1369 if status_change_count_before_expansion == context.shell.last_exit_status_change_count()
1375 {
1376 context.shell.set_last_exit_status(0);
1377 }
1378
1379 Ok(ExecutionResult::new(context.shell.last_exit_status()).into())
1382 }
1383 }
1384}
1385
1386async fn execute_command(
1387 context: PipelineExecutionContext<'_, impl extensions::ShellExtensions>,
1388 params: ExecutionParameters,
1389 cmd_name: String,
1390 assignments: &[&ast::Assignment],
1391 args: Vec<CommandArg>,
1392) -> Result<ExecutionSpawnResult, error::Error> {
1393 let mut scoped;
1396 let mut shell = match context.shell {
1397 commands::ShellForCommand::ParentShell(shell) => {
1398 scoped = shell.enter_scope(EnvironmentScope::Command);
1401 commands::ShellForCommand::ParentShell(&mut scoped)
1402 }
1403 commands::ShellForCommand::OwnedShell { mut target, parent } => {
1404 target.env_mut().push_scope(EnvironmentScope::Command);
1406 commands::ShellForCommand::OwnedShell { target, parent }
1407 }
1408 };
1409
1410 for assignment in assignments {
1411 apply_assignment(
1413 assignment,
1414 &mut shell,
1415 ¶ms,
1416 true,
1417 Some(EnvironmentScope::Command),
1418 EnvironmentScope::Command,
1419 )
1420 .await?;
1421 }
1422
1423 if shell.options().print_commands_and_arguments {
1424 shell
1425 .trace_command(
1426 ¶ms,
1427 args.iter().map(|arg| arg.quote_for_tracing()).join(" "),
1428 )
1429 .await;
1430 }
1431
1432 let mut cmd = commands::SimpleCommand::new(shell, params, cmd_name, args);
1434 cmd.process_group_id = context.process_group_id;
1435
1436 let _ = commands::on_preexecute(&mut cmd).await;
1438
1439 cmd.execute().await
1442}
1443
1444fn tokenize_alias_body(
1452 shell: &Shell<impl extensions::ShellExtensions>,
1453 alias_value: &str,
1454) -> Vec<String> {
1455 let options = shell.parser_options().tokenizer_options();
1458
1459 shrub_parser::tokenize_str_with_options(alias_value, &options).map_or_else(
1462 |_| {
1463 alias_value
1464 .split_ascii_whitespace()
1465 .map(str::to_owned)
1466 .collect()
1467 },
1468 |tokens| tokens.iter().map(|t| t.to_str().to_owned()).collect(),
1469 )
1470}
1471
1472async fn expand_words(
1475 shell: &mut Shell<impl extensions::ShellExtensions>,
1476 params: &ExecutionParameters,
1477 words: impl IntoIterator<Item = impl AsRef<str>>,
1478) -> Result<Vec<String>, error::Error> {
1479 let mut fields = vec![];
1482 for word in words {
1483 fields.extend(expansion::full_expand_and_split_word(shell, params, word).await?);
1484 }
1485 Ok(fields)
1486}
1487
1488async fn expand_assignment(
1489 shell: &mut Shell<impl extensions::ShellExtensions>,
1490 params: &ExecutionParameters,
1491 assignment: &ast::Assignment,
1492) -> Result<ast::Assignment, error::Error> {
1493 let value = expand_assignment_value(shell, params, &assignment.value).await?;
1494 Ok(ast::Assignment {
1495 name: basic_expand_assignment_name(shell, params, &assignment.name).await?,
1496 value,
1497 append: assignment.append,
1498 loc: assignment.loc.clone(),
1499 })
1500}
1501
1502async fn basic_expand_assignment_name(
1503 shell: &mut Shell<impl extensions::ShellExtensions>,
1504 params: &ExecutionParameters,
1505 name: &ast::AssignmentName,
1506) -> Result<ast::AssignmentName, error::Error> {
1507 match name {
1508 ast::AssignmentName::VariableName(name) => {
1509 let expanded = expansion::basic_expand_word(shell, params, name).await?;
1510 Ok(ast::AssignmentName::VariableName(expanded))
1511 }
1512 ast::AssignmentName::ArrayElementName(name, index) => {
1513 let expanded_name = expansion::basic_expand_word(shell, params, name).await?;
1514 let expanded_index = expansion::basic_expand_word(shell, params, index).await?;
1515 Ok(ast::AssignmentName::ArrayElementName(
1516 expanded_name,
1517 expanded_index,
1518 ))
1519 }
1520 }
1521}
1522
1523async fn expand_assignment_value(
1524 shell: &mut Shell<impl extensions::ShellExtensions>,
1525 params: &ExecutionParameters,
1526 value: &ast::AssignmentValue,
1527) -> Result<ast::AssignmentValue, error::Error> {
1528 let expanded = match value {
1529 ast::AssignmentValue::Scalar(s) => {
1530 let expanded_word = expansion::basic_expand_assignment_word(shell, params, s).await?;
1531 ast::AssignmentValue::Scalar(ast::Word::from(expanded_word))
1532 }
1533 ast::AssignmentValue::Array(arr) => {
1534 let mut expanded_values = vec![];
1535 for (key, value) in arr {
1536 if let Some(k) = key {
1537 let expanded_key = expansion::basic_expand_assignment_word(shell, params, k)
1538 .await?
1539 .into();
1540 let expanded_value =
1541 expansion::basic_expand_assignment_word(shell, params, value)
1542 .await?
1543 .into();
1544 expanded_values.push((Some(expanded_key), expanded_value));
1545 } else {
1546 let split_expanded_value =
1549 expansion::full_expand_and_split_word(shell, params, value).await?;
1550 for expanded_value in split_expanded_value {
1551 expanded_values.push((None, expanded_value.into()));
1552 }
1553 }
1554 }
1555
1556 ast::AssignmentValue::Array(expanded_values)
1557 }
1558 };
1559
1560 Ok(expanded)
1561}
1562
1563#[expect(clippy::too_many_lines)]
1564async fn apply_assignment(
1565 assignment: &ast::Assignment,
1566 shell: &mut Shell<impl extensions::ShellExtensions>,
1567 params: &ExecutionParameters,
1568 mut export: bool,
1569 required_scope: Option<EnvironmentScope>,
1570 creation_scope: EnvironmentScope,
1571) -> Result<(), error::Error> {
1572 let mut array_index;
1575 let variable_name = match &assignment.name {
1576 ast::AssignmentName::VariableName(name) => {
1577 array_index = None;
1578 name
1579 }
1580 ast::AssignmentName::ArrayElementName(name, index) => {
1581 let expanded = expansion::basic_expand_word(shell, params, index).await?;
1582 array_index = Some(expanded);
1583 name
1584 }
1585 };
1586
1587 let new_value = match &assignment.value {
1589 ast::AssignmentValue::Scalar(unexpanded_value) => {
1590 let value =
1591 expansion::basic_expand_assignment_word(shell, params, unexpanded_value).await?;
1592 ShellValueLiteral::Scalar(value)
1593 }
1594 ast::AssignmentValue::Array(unexpanded_values) => {
1595 let mut elements = vec![];
1596 for (unexpanded_key, unexpanded_value) in unexpanded_values {
1597 let key = match unexpanded_key {
1598 Some(unexpanded_key) => Some(
1599 expansion::basic_expand_assignment_word(shell, params, unexpanded_key)
1600 .await?,
1601 ),
1602 None => None,
1603 };
1604
1605 if key.is_some() {
1606 let value =
1607 expansion::basic_expand_assignment_word(shell, params, unexpanded_value)
1608 .await?;
1609 elements.push((key, value));
1610 } else {
1611 let values =
1614 expansion::full_expand_and_split_word(shell, params, unexpanded_value)
1615 .await?;
1616 for value in values {
1617 elements.push((None, value));
1618 }
1619 }
1620 }
1621 ShellValueLiteral::Array(ArrayLiteral(elements))
1622 }
1623 };
1624
1625 if shell.options().print_commands_and_arguments {
1626 let op = if assignment.append { "+=" } else { "=" };
1627 shell
1628 .trace_command(params, std::format!("{}{op}{new_value}", assignment.name))
1629 .await;
1630 }
1631
1632 if let Some(idx) = &array_index {
1634 let will_be_indexed_array =
1639 if let Some((_, existing_value)) = shell.env().get(variable_name) {
1640 !matches!(
1641 existing_value.value(),
1642 ShellValue::AssociativeArray(_)
1643 | ShellValue::Unset(ShellValueUnsetType::AssociativeArray)
1644 )
1645 } else {
1646 true
1647 };
1648
1649 if will_be_indexed_array {
1650 array_index = Some(
1651 arithmetic::expand_and_eval(shell, params, idx.as_str(), false)
1652 .await?
1653 .to_string(),
1654 );
1655 }
1656 }
1657
1658 let export_variables_on_modification = shell.options().export_variables_on_modification;
1660
1661 if required_scope.is_some()
1664 && shell
1665 .env()
1666 .get(variable_name.as_str())
1667 .is_some_and(|(_, var)| var.is_readonly())
1668 {
1669 let err = error::Error::from(error::ErrorKind::ReadonlyVariable);
1670 let _ = shell.display_error(&mut params.stderr(shell), &err);
1671 return Ok(());
1672 }
1673
1674 let lookup = required_scope.map_or(
1679 EnvironmentLookup::Anywhere,
1680 EnvironmentLookup::OnlyInCurrent,
1681 );
1682 let existing_value = shell
1683 .env_mut()
1684 .get_mut_using_policy(variable_name.as_str(), lookup);
1685 if let Some(existing_value) = existing_value {
1686 if let Some(array_index) = array_index {
1687 match new_value {
1688 ShellValueLiteral::Scalar(s) => {
1689 existing_value.assign_at_index(array_index, s, assignment.append)?;
1690 }
1691 ShellValueLiteral::Array(_) => {
1692 return error::unimp("replacing an array item with an array");
1693 }
1694 }
1695 } else {
1696 if !export
1697 && export_variables_on_modification
1698 && !matches!(new_value, ShellValueLiteral::Array(_))
1699 {
1700 export = true;
1701 }
1702
1703 existing_value.assign(new_value, assignment.append)?;
1704 }
1705
1706 if export {
1707 existing_value.export();
1708 }
1709
1710 return Ok(());
1712 }
1713
1714 let new_value = if let Some(array_index) = array_index {
1716 match new_value {
1717 ShellValueLiteral::Scalar(s) => {
1718 ShellValue::indexed_array_from_literals(ArrayLiteral(vec![(Some(array_index), s)]))
1719 }
1720 ShellValueLiteral::Array(_) => {
1721 return error::unimp("cannot assign list to array member");
1722 }
1723 }
1724 } else {
1725 match new_value {
1726 ShellValueLiteral::Scalar(s) => {
1727 export = export || shell.options().export_variables_on_modification;
1728
1729 let s = match shell.env().get(variable_name.as_str()) {
1734 Some((_, visible)) if assignment.append => {
1735 let mut appended = visible.clone();
1736 appended.assign(ShellValueLiteral::Scalar(s), true)?;
1737 appended.value().to_cow_str(shell).into_owned()
1738 }
1739 _ => s,
1740 };
1741 ShellValue::String(s)
1742 }
1743 ShellValueLiteral::Array(values) => ShellValue::indexed_array_from_literals(values),
1744 }
1745 };
1746
1747 let mut new_var = ShellVariable::new(new_value);
1748
1749 if export {
1750 new_var.export();
1751 }
1752
1753 shell.env_mut().add(variable_name, new_var, creation_scope)
1754}
1755
1756#[expect(clippy::too_many_lines)]
1757pub(crate) async fn setup_redirect(
1758 shell: &mut Shell<impl extensions::ShellExtensions>,
1759 params: &'_ mut ExecutionParameters,
1760 redirect: &ast::IoRedirect,
1761) -> Result<(), error::Error> {
1762 match redirect {
1763 ast::IoRedirect::OutputAndError(f, append) => {
1764 let mut expanded_fields =
1765 expansion::full_expand_and_split_word(shell, params, f).await?;
1766 if expanded_fields.len() != 1 {
1767 return Err(error::ErrorKind::InvalidRedirection.into());
1768 }
1769
1770 let expanded_file_path = expanded_fields.remove(0);
1771 setup_redirect_output_and_error_to(shell, params, &expanded_file_path, *append)?;
1772 }
1773
1774 ast::IoRedirect::File(specified_fd_num, kind, target) => {
1775 match target {
1776 ast::IoFileRedirectTarget::Filename(f) => {
1777 let mut options = std::fs::File::options();
1778
1779 let mut expanded_fields =
1780 expansion::full_expand_and_split_word(shell, params, f).await?;
1781
1782 if expanded_fields.len() != 1 {
1783 return Err(error::ErrorKind::InvalidRedirection.into());
1784 }
1785
1786 let expanded_file_path: PathBuf =
1787 shell.absolute_path(Path::new(expanded_fields.remove(0).as_str()));
1788
1789 let default_fd_if_unspecified = get_default_fd_for_redirect_kind(kind);
1790 match kind {
1791 ast::IoFileRedirectKind::Read => {
1792 options.read(true);
1793 }
1794 ast::IoFileRedirectKind::Write => {
1795 if shell
1796 .options()
1797 .disallow_overwriting_regular_files_via_output_redirection
1798 {
1799 if !expanded_file_path.is_file() {
1803 options.create(true);
1804 } else {
1805 options.create_new(true);
1806 }
1807 options.write(true);
1808 } else {
1809 options.create(true);
1810 options.write(true);
1811 options.truncate(true);
1812 }
1813 }
1814 ast::IoFileRedirectKind::Append => {
1815 options.create(true);
1816 options.append(true);
1817 }
1818 ast::IoFileRedirectKind::ReadAndWrite => {
1819 options.create(true);
1820 options.read(true);
1821 options.write(true);
1822 }
1823 ast::IoFileRedirectKind::Clobber => {
1824 options.create(true);
1825 options.write(true);
1826 options.truncate(true);
1827 }
1828 ast::IoFileRedirectKind::DuplicateInput => {
1829 options.read(true);
1830 }
1831 ast::IoFileRedirectKind::DuplicateOutput => {
1832 options.create(true);
1833 options.write(true);
1834 }
1835 }
1836
1837 let fd_num = specified_fd_num.unwrap_or(default_fd_if_unspecified);
1838
1839 let opened_file = shell
1840 .open_file(&options, &expanded_file_path, params)
1841 .map_err(|err| {
1842 error::ErrorKind::RedirectionFailure(
1843 expanded_file_path.to_string_lossy().to_string(),
1844 err.to_string(),
1845 )
1846 })?;
1847
1848 params.open_files.set_fd(fd_num, opened_file);
1849 }
1850
1851 ast::IoFileRedirectTarget::Fd(fd) => {
1852 let default_fd_if_unspecified = match kind {
1853 ast::IoFileRedirectKind::DuplicateInput => 0,
1854 ast::IoFileRedirectKind::DuplicateOutput => 1,
1855 _ => {
1856 return error::unimp("unexpected redirect kind");
1857 }
1858 };
1859
1860 let fd_num = specified_fd_num.unwrap_or(default_fd_if_unspecified);
1861
1862 if let Some(target_file) = params.try_fd(shell, *fd) {
1863 params.open_files.set_fd(fd_num, target_file);
1864 } else {
1865 return Err(error::ErrorKind::BadFileDescriptor(*fd).into());
1866 }
1867 }
1868
1869 ast::IoFileRedirectTarget::Duplicate(word) => {
1870 let default_fd_if_unspecified = match kind {
1871 ast::IoFileRedirectKind::DuplicateInput => 0,
1872 ast::IoFileRedirectKind::DuplicateOutput => 1,
1873 _ => {
1874 return error::unimp("unexpected redirect kind");
1875 }
1876 };
1877
1878 let fd_num = specified_fd_num.unwrap_or(default_fd_if_unspecified);
1879
1880 let mut expanded_fields =
1881 expansion::full_expand_and_split_word(shell, params, word).await?;
1882
1883 if expanded_fields.len() != 1 {
1884 return Err(error::ErrorKind::InvalidRedirection.into());
1885 }
1886
1887 let mut expanded = expanded_fields.remove(0);
1888
1889 let dash = if expanded.ends_with('-') {
1890 expanded.pop();
1891 true
1892 } else {
1893 false
1894 };
1895
1896 if expanded.is_empty() {
1897 } else if expanded.chars().all(|c: char| c.is_ascii_digit()) {
1899 let source_fd_num = expanded
1900 .parse::<ShellFd>()
1901 .map_err(|_| error::ErrorKind::InvalidRedirection)?;
1902
1903 let Some(target_file) = params.try_fd(shell, source_fd_num) else {
1906 return Err(error::ErrorKind::BadFileDescriptor(source_fd_num).into());
1907 };
1908
1909 params.open_files.set_fd(fd_num, target_file);
1910 } else if fd_num == 1 && !dash {
1911 setup_redirect_output_and_error_to(
1915 shell, params, &expanded, false, )?;
1917 } else {
1918 return Err(error::ErrorKind::InvalidRedirection.into());
1919 }
1920
1921 if dash {
1922 params.open_files.remove_fd(fd_num);
1924 }
1925 }
1926
1927 ast::IoFileRedirectTarget::ProcessSubstitution(substitution_kind, subshell_cmd) => {
1928 match kind {
1929 ast::IoFileRedirectKind::Read
1930 | ast::IoFileRedirectKind::Write
1931 | ast::IoFileRedirectKind::Append
1932 | ast::IoFileRedirectKind::ReadAndWrite
1933 | ast::IoFileRedirectKind::Clobber => {
1934 let (substitution_fd, substitution_file) = setup_process_substitution(
1935 shell,
1936 params,
1937 substitution_kind,
1938 subshell_cmd,
1939 )?;
1940
1941 let target_file = substitution_file.clone();
1942 params.open_files.set_fd(substitution_fd, substitution_file);
1943
1944 let fd_num = specified_fd_num
1945 .unwrap_or_else(|| get_default_fd_for_redirect_kind(kind));
1946
1947 params.open_files.set_fd(fd_num, target_file);
1948 }
1949 _ => return error::unimp("invalid process substitution"),
1950 }
1951 }
1952 }
1953 }
1954
1955 ast::IoRedirect::HereDocument(fd_num, io_here) => {
1956 let fd_num = fd_num.unwrap_or(0);
1958
1959 let io_here_doc = if io_here.requires_expansion {
1961 expansion::basic_expand_heredoc_word(shell, params, &io_here.doc).await?
1962 } else {
1963 io_here.doc.flatten()
1964 };
1965
1966 let f = setup_open_file_with_contents(io_here_doc.as_str())?;
1967
1968 params.open_files.set_fd(fd_num, f);
1969 }
1970
1971 ast::IoRedirect::HereString(fd_num, word) => {
1972 let fd_num = fd_num.unwrap_or(0);
1974
1975 let mut expanded_word = expansion::basic_expand_word(shell, params, word).await?;
1976 expanded_word.push('\n');
1977
1978 let f = setup_open_file_with_contents(expanded_word.as_str())?;
1979
1980 params.open_files.set_fd(fd_num, f);
1981 }
1982 }
1983
1984 Ok(())
1985}
1986
1987fn setup_redirect_output_and_error_to(
1997 shell: &Shell<impl extensions::ShellExtensions>,
1998 params: &mut ExecutionParameters,
1999 file_path: &str,
2000 append: bool,
2001) -> Result<(), error::Error> {
2002 let abs_file_path: PathBuf = shell.absolute_path(Path::new(file_path));
2003
2004 let mut file_options = std::fs::File::options();
2005 file_options
2006 .create(true)
2007 .write(true)
2008 .truncate(!append)
2009 .append(append);
2010
2011 let stdout_file = shell
2012 .open_file(&file_options, &abs_file_path, params)
2013 .map_err(|err| {
2014 error::ErrorKind::RedirectionFailure(
2015 abs_file_path.to_string_lossy().to_string(),
2016 err.to_string(),
2017 )
2018 })?;
2019
2020 let stderr_file = stdout_file.clone();
2021
2022 params.open_files.set_fd(OpenFiles::STDOUT_FD, stdout_file);
2023 params.open_files.set_fd(OpenFiles::STDERR_FD, stderr_file);
2024
2025 Ok(())
2026}
2027
2028const fn get_default_fd_for_redirect_kind(kind: &ast::IoFileRedirectKind) -> ShellFd {
2029 match kind {
2030 ast::IoFileRedirectKind::Read => 0,
2031 ast::IoFileRedirectKind::Write => 1,
2032 ast::IoFileRedirectKind::Append => 1,
2033 ast::IoFileRedirectKind::ReadAndWrite => 0,
2034 ast::IoFileRedirectKind::Clobber => 1,
2035 ast::IoFileRedirectKind::DuplicateInput => 0,
2036 ast::IoFileRedirectKind::DuplicateOutput => 1,
2037 }
2038}
2039
2040fn setup_process_substitution(
2041 shell: &Shell<impl extensions::ShellExtensions>,
2042 params: &ExecutionParameters,
2043 kind: &ast::ProcessSubstitutionKind,
2044 subshell_cmd: &ast::SubshellCommand,
2045) -> Result<(ShellFd, OpenFile), error::Error> {
2046 let mut subshell = shell.clone();
2049
2050 let mut child_params = params.clone();
2052 child_params.process_group_policy = ProcessGroupPolicy::SameProcessGroup;
2053
2054 let (reader, writer) = sys::pipes::pipe()?;
2056 let (reader, writer) = (reader.into(), writer.into());
2057
2058 let target_file = match kind {
2059 ast::ProcessSubstitutionKind::Read => {
2060 child_params.open_files.set_fd(OpenFiles::STDOUT_FD, writer);
2061 reader
2062 }
2063 ast::ProcessSubstitutionKind::Write => {
2064 child_params.open_files.set_fd(OpenFiles::STDIN_FD, reader);
2065 writer
2066 }
2067 };
2068
2069 let subshell_cmd = subshell_cmd.to_owned();
2072 sys::tasks::spawn(async move {
2073 let _ = subshell_cmd
2075 .list
2076 .execute(&mut subshell, &child_params)
2077 .await;
2078 });
2079
2080 let mut candidate_fd_num = 63;
2083 while params.open_files.contains_fd(candidate_fd_num) {
2084 candidate_fd_num -= 1;
2085 if candidate_fd_num == 0 {
2086 return error::unimp("no available file descriptors");
2087 }
2088 }
2089
2090 Ok((candidate_fd_num, target_file))
2091}
2092
2093fn setup_open_file_with_contents(contents: &str) -> Result<OpenFile, error::Error> {
2094 let (reader, mut writer) = sys::pipes::pipe()?;
2095
2096 let bytes = contents.as_bytes();
2097
2098 #[cfg(any(target_os = "linux", target_os = "android"))]
2100 {
2101 use std::os::fd::AsFd as _;
2102
2103 let len = i32::try_from(bytes.len())
2104 .map_err(|_err| error::Error::from(error::ErrorKind::TooMuchData))?;
2105 nix::fcntl::fcntl(reader.as_fd(), nix::fcntl::FcntlArg::F_SETPIPE_SZ(len))?;
2106 }
2107
2108 writer.write_all(bytes)?;
2109 drop(writer);
2110
2111 Ok(reader.into())
2112}
2113
2114#[cfg(test)]
2117#[cfg(target_os = "linux")] #[allow(clippy::panic_in_result_fn, reason = "assertions in a fallible test")]
2119mod tests {
2120 use super::*;
2121 use crate::{ProfileLoadBehavior, RcLoadBehavior};
2122
2123 #[tokio::test]
2129 async fn futures_for_a_simple_command_stay_small() -> anyhow::Result<()> {
2130 let mut shell = Shell::builder()
2131 .profile(ProfileLoadBehavior::Skip)
2132 .rc(RcLoadBehavior::Skip)
2133 .build()
2134 .await?;
2135 let params = shell.default_exec_params();
2136
2137 let program = shell.parse_string("true")?;
2138 let list = &program.complete_commands[0];
2139 let and_or = &list.0[0].0;
2140 let pipeline = &and_or.first;
2141 let command = &pipeline.seq[0];
2142 let ast::Command::Simple(simple) = command else {
2143 anyhow::bail!("expected a simple command");
2144 };
2145
2146 let mut sizes = vec![];
2147 let context = PipelineExecutionContext {
2148 shell: commands::ShellForCommand::ParentShell(&mut shell),
2149 process_group_id: None,
2150 };
2151 let future = simple.execute_in_pipeline(context, params.clone());
2152 sizes.push((
2153 "SimpleCommand::execute_in_pipeline",
2154 size_of_val(&*future),
2155 3072,
2156 ));
2157 drop(future);
2158
2159 let context = PipelineExecutionContext {
2160 shell: commands::ShellForCommand::ParentShell(&mut shell),
2161 process_group_id: None,
2162 };
2163 let future = command.execute_in_pipeline(context, params.clone());
2164 sizes.push(("Command::execute_in_pipeline", size_of_val(&*future), 768));
2165 drop(future);
2166
2167 let future = pipeline.execute(&mut shell, ¶ms);
2168 sizes.push(("Pipeline::execute", size_of_val(&*future), 768));
2169 drop(future);
2170
2171 let future = and_or.execute(&mut shell, ¶ms);
2172 sizes.push(("AndOrList::execute", size_of_val(&*future), 256));
2173 drop(future);
2174
2175 let future = list.execute(&mut shell, ¶ms);
2176 sizes.push(("CompoundList::execute", size_of_val(&*future), 128));
2177 drop(future);
2178
2179 for (name, size, limit) in sizes {
2180 assert!(
2181 size <= limit,
2182 "{name}'s future takes {size} bytes; the limit is {limit}"
2183 );
2184 }
2185
2186 Ok(())
2187 }
2188}