kodegen_bash_shell/core/
shell.rs

1use std::borrow::Cow;
2use std::collections::HashMap;
3use std::io::{Read, Write};
4use std::path::{Path, PathBuf};
5use std::sync::Arc;
6
7use normalize_path::NormalizePath;
8use tokio::sync::Mutex;
9
10use super::arithmetic::Evaluatable;
11use super::env::{EnvironmentLookup, EnvironmentScope, ShellEnvironment};
12use super::interp::{self, Execute, ExecutionParameters};
13use super::options::RuntimeOptions;
14use super::results::ExecutionSpawnResult;
15use super::sys::fs::PathExt;
16use super::variables::{self, ShellVariable};
17use super::{
18    ExecutionControlFlow, ExecutionResult, ProcessGroupPolicy, history, interfaces, pathcache,
19    pathsearch, scripts, trace_categories, wellknownvars,
20};
21use super::{
22    builtins, commands, completion, env, error, expansion, functions, jobs, keywords, openfiles,
23    prompt, sys::users, traps,
24};
25
26/// Type for storing a key bindings helper.
27pub type KeyBindingsHelper = Arc<Mutex<dyn interfaces::KeyBindings>>;
28
29/// Type for storing an error formatter.
30pub type ErrorFormatterHelper = Arc<Mutex<dyn error::ErrorFormatter>>;
31
32/// Type alias for shell file descriptors.
33pub type ShellFd = i32;
34
35/// Represents an instance of a shell.
36pub struct Shell {
37    /// Trap handler configuration for the shell.
38    pub traps: traps::TrapHandlerConfig,
39
40    /// Manages files opened and accessible via redirection operators.
41    open_files: openfiles::OpenFiles,
42
43    /// The current working directory.
44    working_dir: PathBuf,
45
46    /// The shell environment, containing shell variables.
47    pub env: ShellEnvironment,
48
49    /// Shell function definitions.
50    funcs: functions::FunctionEnv,
51
52    /// Runtime shell options.
53    pub options: RuntimeOptions,
54
55    /// State of managed jobs.
56    pub jobs: jobs::JobManager,
57
58    /// Shell aliases.
59    pub aliases: HashMap<String, String>,
60
61    /// The status of the last completed command.
62    last_exit_status: u8,
63
64    /// The status of each of the commands in the last pipeline.
65    pub last_pipeline_statuses: Vec<u8>,
66
67    /// Clone depth from the original ancestor shell.
68    depth: usize,
69
70    /// Shell name (a.k.a. $0)
71    pub shell_name: Option<String>,
72
73    /// Shell version
74    version: Option<String>,
75
76    /// Positional parameters stack ($1 and beyond)
77    pub positional_parameters: Vec<String>,
78
79    /// Detailed display string for the shell
80    product_display_str: Option<String>,
81
82    /// Script call stack.
83    script_call_stack: scripts::CallStack,
84
85    /// Function call stack.
86    function_call_stack: functions::CallStack,
87
88    /// Directory stack used by pushd et al.
89    pub directory_stack: Vec<PathBuf>,
90
91    /// Current line number being processed.
92    current_line_number: u32,
93
94    /// Completion configuration.
95    pub completion_config: completion::Config,
96
97    /// Shell built-in commands.
98    builtins: HashMap<String, builtins::Registration>,
99
100    /// Shell program location cache.
101    pub program_location_cache: pathcache::PathCache,
102
103    /// Last "SECONDS" captured time.
104    last_stopwatch_time: std::time::SystemTime,
105
106    /// Last "SECONDS" offset requested.
107    last_stopwatch_offset: u32,
108
109    /// Key bindings for the shell, optionally implemented by an interactive shell.
110    key_bindings: Option<KeyBindingsHelper>,
111
112    /// History of commands executed in the shell.
113    history: Option<history::History>,
114
115    /// Error formatter for customizing error display.
116    error_formatter: ErrorFormatterHelper,
117}
118
119impl Clone for Shell {
120    fn clone(&self) -> Self {
121        Self {
122            traps: self.traps.clone(),
123            open_files: self.open_files.clone(),
124            working_dir: self.working_dir.clone(),
125            env: self.env.clone(),
126            funcs: self.funcs.clone(),
127            options: self.options.clone(),
128            jobs: jobs::JobManager::new(),
129            aliases: self.aliases.clone(),
130            last_exit_status: self.last_exit_status,
131            last_pipeline_statuses: self.last_pipeline_statuses.clone(),
132            positional_parameters: self.positional_parameters.clone(),
133            shell_name: self.shell_name.clone(),
134            version: self.version.clone(),
135            product_display_str: self.product_display_str.clone(),
136            function_call_stack: self.function_call_stack.clone(),
137            script_call_stack: self.script_call_stack.clone(),
138            directory_stack: self.directory_stack.clone(),
139            current_line_number: self.current_line_number,
140            completion_config: self.completion_config.clone(),
141            builtins: self.builtins.clone(),
142            program_location_cache: self.program_location_cache.clone(),
143            last_stopwatch_time: self.last_stopwatch_time,
144            last_stopwatch_offset: self.last_stopwatch_offset,
145            key_bindings: self.key_bindings.clone(),
146            history: self.history.clone(),
147            error_formatter: self.error_formatter.clone(),
148            depth: self.depth + 1,
149        }
150    }
151}
152
153impl AsRef<Self> for Shell {
154    fn as_ref(&self) -> &Self {
155        self
156    }
157}
158
159impl AsMut<Self> for Shell {
160    fn as_mut(&mut self) -> &mut Self {
161        self
162    }
163}
164
165pub use shell_builder::State as ShellBuilderState;
166
167impl<S: shell_builder::IsComplete> ShellBuilder<S> {
168    /// Returns a new shell instance created with the options provided
169    pub async fn build(self) -> Result<Shell, error::Error> {
170        let options = self.build_settings();
171
172        Shell::new(options).await
173    }
174}
175
176impl<S: shell_builder::State> ShellBuilder<S> {
177    /// Add a disabled option
178    pub fn disable_option(mut self, option: impl Into<String>) -> Self {
179        self.disabled_options.push(option.into());
180        self
181    }
182
183    /// Add an enabled option
184    pub fn enable_option(mut self, option: impl Into<String>) -> Self {
185        self.enabled_options.push(option.into());
186        self
187    }
188
189    /// Add many disabled options
190    pub fn disable_options(mut self, options: impl IntoIterator<Item: Into<String>>) -> Self {
191        self.disabled_options
192            .extend(options.into_iter().map(Into::into));
193        self
194    }
195
196    /// Add many enabled options
197    pub fn enable_options(mut self, options: impl IntoIterator<Item: Into<String>>) -> Self {
198        self.enabled_options
199            .extend(options.into_iter().map(Into::into));
200        self
201    }
202
203    /// Add a disabled shopt option
204    pub fn disable_shopt_option(mut self, option: impl Into<String>) -> Self {
205        self.disabled_shopt_options.push(option.into());
206        self
207    }
208
209    /// Add an enabled shopt option
210    pub fn enable_shopt_option(mut self, option: impl Into<String>) -> Self {
211        self.enabled_shopt_options.push(option.into());
212        self
213    }
214
215    /// Add many disabled shopt options
216    pub fn disable_shopt_options(mut self, options: impl IntoIterator<Item: Into<String>>) -> Self {
217        self.disabled_shopt_options
218            .extend(options.into_iter().map(Into::into));
219        self
220    }
221
222    /// Add many enabled shopt options
223    pub fn enable_shopt_options(mut self, options: impl IntoIterator<Item: Into<String>>) -> Self {
224        self.enabled_shopt_options
225            .extend(options.into_iter().map(Into::into));
226        self
227    }
228
229    /// Add a single builtin registration
230    pub fn builtin(mut self, name: impl Into<String>, reg: builtins::Registration) -> Self {
231        self.builtins.insert(name.into(), reg);
232        self
233    }
234
235    /// Add many builtin registrations
236    pub fn builtins(
237        mut self,
238        builtins: impl IntoIterator<Item = (String, builtins::Registration)>,
239    ) -> Self {
240        self.builtins.extend(builtins);
241        self
242    }
243}
244
245/// Options for creating a new shell.
246#[derive(Default, bon::Builder)]
247#[builder(
248    builder_type(
249        name = ShellBuilder,
250        doc {
251        /// Builder for [Shell]
252    }),
253    finish_fn(
254        name = build_settings,
255        vis = "pub(self)",
256    ),
257    start_fn(
258        vis = "pub(self)"
259    )
260)]
261pub struct CreateOptions {
262    /// Disabled options.
263    #[builder(field)]
264    pub disabled_options: Vec<String>,
265    /// Enabled options.
266    #[builder(field)]
267    pub enabled_options: Vec<String>,
268    /// Disabled shopt options.
269    #[builder(field)]
270    pub disabled_shopt_options: Vec<String>,
271    /// Enabled shopt options.
272    #[builder(field)]
273    pub enabled_shopt_options: Vec<String>,
274    /// Registered builtins. Defaults to full bash-compatible builtins.
275    #[builder(field)]
276    pub builtins: HashMap<String, builtins::Registration>,
277    /// Optional override for initial working directory.
278    /// If None, defaults to std::env::current_dir().
279    /// When provided, the shell will start in this directory instead of
280    /// inheriting the process's current directory.
281    ///
282    /// This is critical for HTTP-based MCP tools where the shell should
283    /// start in the CLIENT's working directory, not the SERVER's.
284    pub working_dir: Option<PathBuf>,
285    /// Disallow overwriting regular files via output redirection.
286    #[builder(default)]
287    pub disallow_overwriting_regular_files_via_output_redirection: bool,
288    /// Do not execute commands.
289    #[builder(default)]
290    pub do_not_execute_commands: bool,
291    /// Exit after one command.
292    #[builder(default)]
293    pub exit_after_one_command: bool,
294    /// Whether the shell is interactive.
295    #[builder(default)]
296    pub interactive: bool,
297    /// Whether the shell is a login shell.
298    #[builder(default)]
299    pub login: bool,
300    /// Whether to skip using a readline-like interface for input.
301    #[builder(default)]
302    pub no_editing: bool,
303    /// Whether to skip sourcing the system profile.
304    #[builder(default)]
305    pub no_profile: bool,
306    /// Whether to skip sourcing the user's rc file.
307    #[builder(default)]
308    pub no_rc: bool,
309    /// Explicit override of rc file to load in interactive mode.
310    pub rc_file: Option<PathBuf>,
311    /// Whether to skip inheriting environment variables from the calling process.
312    #[builder(default)]
313    pub do_not_inherit_env: bool,
314    /// Provides a set of initial open files to be tracked by the shell.
315    pub fds: Option<HashMap<ShellFd, openfiles::OpenFile>>,
316    /// Whether the shell is in POSIX compliance mode.
317    #[builder(default)]
318    pub posix: bool,
319    /// Whether to print commands and arguments as they are read.
320    #[builder(default)]
321    pub print_commands_and_arguments: bool,
322    /// Whether commands are being read from stdin.
323    #[builder(default)]
324    pub read_commands_from_stdin: bool,
325    /// The name of the shell.
326    pub shell_name: Option<String>,
327    /// Optionally provides a display string describing the version and variant of the shell.
328    pub shell_product_display_str: Option<String>,
329    /// Whether to run in maximal POSIX sh compatibility mode.
330    #[builder(default)]
331    pub sh_mode: bool,
332    /// Whether to treat expansion of unset variables as an error.
333    #[builder(default)]
334    pub treat_unset_variables_as_error: bool,
335    /// Whether to print verbose output.
336    #[builder(default)]
337    pub verbose: bool,
338    /// Whether the shell is in command string mode (-c).
339    #[builder(default)]
340    pub command_string_mode: bool,
341    /// Maximum function call depth.
342    pub max_function_call_depth: Option<usize>,
343    /// Key bindings helper for the shell to use.
344    pub key_bindings: Option<KeyBindingsHelper>,
345    /// Error formatter helper for the shell to use.
346    pub error_formatter: Option<ErrorFormatterHelper>,
347    /// Brush implementation version.
348    pub shell_version: Option<String>,
349}
350
351impl Shell {
352    /// Create an instance of [Shell] using the builder syntax.
353    /// By default, includes the full set of bash-compatible builtins.
354    pub fn builder() -> ShellBuilder<shell_builder::Empty> {
355        CreateOptions::builder().builtins(crate::builtins::default_builtins(
356            crate::builtins::BuiltinSet::BashMode,
357        ))
358    }
359
360    /// Returns a new shell instance created with the given options.
361    ///
362    /// # Arguments
363    ///
364    /// * `options` - The options to use when creating the shell.
365    pub async fn new(options: CreateOptions) -> Result<Self, error::Error> {
366        // Instantiate the shell with some defaults.
367        let mut shell = Self {
368            traps: traps::TrapHandlerConfig::default(),
369            open_files: openfiles::OpenFiles::new(),
370            // Use explicitly provided directory (from client context) or fallback to process cwd
371            working_dir: if let Some(ref dir) = options.working_dir {
372                // Use explicitly provided directory (from client context)
373                dir.clone()
374            } else {
375                // Fallback to process's current directory for backward compatibility
376                std::env::current_dir()?
377            },
378            env: env::ShellEnvironment::new(),
379            funcs: functions::FunctionEnv::default(),
380            options: RuntimeOptions::defaults_from(&options),
381            jobs: jobs::JobManager::new(),
382            aliases: HashMap::default(),
383            last_exit_status: 0,
384            last_pipeline_statuses: vec![0],
385            positional_parameters: vec![],
386            shell_name: options.shell_name,
387            version: options.shell_version,
388            product_display_str: options.shell_product_display_str,
389            function_call_stack: functions::CallStack::new(),
390            script_call_stack: scripts::CallStack::new(),
391            directory_stack: vec![],
392            current_line_number: 0,
393            completion_config: completion::Config::default(),
394            builtins: options.builtins,
395            program_location_cache: pathcache::PathCache::default(),
396            last_stopwatch_time: std::time::SystemTime::now(),
397            last_stopwatch_offset: 0,
398            key_bindings: options.key_bindings,
399            history: None,
400            error_formatter: options
401                .error_formatter
402                .unwrap_or_else(|| Arc::new(Mutex::new(error::DefaultErrorFormatter::new()))),
403            depth: 0,
404        };
405
406        // Add in any open files provided.
407        if let Some(fds) = options.fds {
408            shell.open_files.update_from(fds.into_iter());
409        }
410
411        // TODO: Without this a script that sets extglob will fail because we
412        // parse the entire script with the same settings.
413        shell.options.extended_globbing = true;
414
415        // Initialize environment.
416        wellknownvars::initialize_vars(&mut shell, options.do_not_inherit_env)?;
417
418        // Set up history, if relevant.
419        if shell.options.enable_command_history {
420            if let Some(history_path) = shell.history_file_path() {
421                let mut options = std::fs::File::options();
422                options.read(true);
423
424                if let Ok(history_file) =
425                    shell.open_file(&options, history_path, &shell.default_exec_params())
426                {
427                    shell.history = Some(history::History::import(history_file)?);
428                }
429            }
430
431            if shell.history.is_none() {
432                shell.history = Some(history::History::default());
433            }
434        }
435
436        // Load profiles/configuration.
437        shell
438            .load_config(
439                options.no_profile,
440                options.no_rc,
441                options.rc_file.as_deref(),
442            )
443            .await?;
444
445        Ok(shell)
446    }
447
448    /// Returns the current source line number being processed.
449    pub const fn current_line_number(&self) -> u32 {
450        self.current_line_number
451    }
452
453    /// Returns the shell's official version string (if available).
454    pub const fn version(&self) -> &Option<String> {
455        &self.version
456    }
457
458    /// Returns the exit status of the last command executed in this shell.
459    pub const fn last_result(&self) -> u8 {
460        self.last_exit_status
461    }
462
463    /// Returns a reference to the current function call stack for the shell.
464    pub const fn function_call_stack(&self) -> &functions::CallStack {
465        &self.function_call_stack
466    }
467
468    /// Returns a reference to the current script call stack for the shell.
469    pub const fn script_call_stack(&self) -> &scripts::CallStack {
470        &self.script_call_stack
471    }
472
473    /// Returns a mutable reference to the last exit status.
474    pub const fn last_exit_status_mut(&mut self) -> &mut u8 {
475        &mut self.last_exit_status
476    }
477
478    /// Returns the key bindings helper for the shell.
479    pub const fn key_bindings(&self) -> &Option<KeyBindingsHelper> {
480        &self.key_bindings
481    }
482
483    /// Returns the registered builtins for the shell.
484    pub const fn builtins(&self) -> &HashMap<String, builtins::Registration> {
485        &self.builtins
486    }
487
488    /// Returns the shell's current working directory.
489    pub fn working_dir(&self) -> &Path {
490        &self.working_dir
491    }
492
493    /// Returns a mutable reference to the shell's current working directory.
494    /// This is only accessible within the crate.
495    pub(crate) const fn working_dir_mut(&mut self) -> &mut PathBuf {
496        &mut self.working_dir
497    }
498
499    /// Returns the product display name for this shell.
500    pub const fn product_display_str(&self) -> &Option<String> {
501        &self.product_display_str
502    }
503
504    /// Returns the function definition environment for this shell.
505    pub const fn funcs(&self) -> &functions::FunctionEnv {
506        &self.funcs
507    }
508
509    /// Tries to undefine a function in the shell's environment. Returns whether or
510    /// not a definition was removed.
511    ///
512    /// # Arguments
513    ///
514    /// * `name` - The name of the function to undefine.
515    pub fn undefine_func(&mut self, name: &str) -> bool {
516        self.funcs.remove(name).is_some()
517    }
518
519    /// Defines a function in the shell's environment. If a function already exists
520    /// with the given name, it is replaced with the new definition.
521    ///
522    /// # Arguments
523    ///
524    /// * `name` - The name of the function to define.
525    /// * `definition` - The function's definition.
526    pub fn define_func(
527        &mut self,
528        name: impl Into<String>,
529        definition: crate::parser::ast::FunctionDefinition,
530    ) {
531        self.funcs.update(name.into(), definition.into());
532    }
533
534    /// Tries to return a mutable reference to the registration for a named function.
535    /// Returns `None` if no such function was found.
536    ///
537    /// # Arguments
538    ///
539    /// * `name` - The name of the function to lookup
540    pub fn func_mut(&mut self, name: &str) -> Option<&mut functions::Registration> {
541        self.funcs.get_mut(name)
542    }
543
544    /// Tries to define a function in the shell's environment using the given
545    /// string as its body.
546    ///
547    /// # Arguments
548    ///
549    /// * `name` - The name of the function
550    /// * `body_text` - The body of the function, expected to start with "()".
551    pub fn define_func_from_str(
552        &mut self,
553        name: impl Into<String>,
554        body_text: &str,
555    ) -> Result<(), error::Error> {
556        let name = name.into();
557
558        let mut parser = create_parser(body_text.as_bytes(), &self.parser_options());
559        let func_body = parser.parse_function_parens_and_body().map_err(|e| {
560            error::Error::from(error::ErrorKind::FunctionParseError(name.clone(), e))
561        })?;
562
563        let def = crate::parser::ast::FunctionDefinition {
564            fname: name.clone().into(),
565            body: func_body,
566            source: String::new(),
567        };
568
569        self.define_func(name, def);
570
571        Ok(())
572    }
573
574    /// Returns the last "SECONDS" captured time.
575    pub const fn last_stopwatch_time(&self) -> std::time::SystemTime {
576        self.last_stopwatch_time
577    }
578
579    /// Returns the last "SECONDS" offset requested.
580    pub const fn last_stopwatch_offset(&self) -> u32 {
581        self.last_stopwatch_offset
582    }
583
584    async fn load_config(
585        &mut self,
586        skip_profile: bool,
587        skip_rc: bool,
588        rc_file: Option<&Path>,
589    ) -> Result<(), error::Error> {
590        let mut params = self.default_exec_params();
591        params.process_group_policy = interp::ProcessGroupPolicy::SameProcessGroup;
592
593        if self.options.login_shell {
594            // --noprofile means skip this.
595            if skip_profile {
596                return Ok(());
597            }
598
599            //
600            // Source /etc/profile if it exists.
601            //
602            // Next source the first of these that exists and is readable (if any):
603            //     * ~/.bash_profile
604            //     * ~/.bash_login
605            //     * ~/.profile
606            //
607            self.source_if_exists(Path::new("/etc/profile"), &params)
608                .await?;
609            if let Some(home_path) = self.home_dir() {
610                // In sh mode, only source .profile
611                // In bash mode, try .bash_profile, then .bash_login, then .profile
612                let sourced = !self.options.sh_mode
613                    && (self
614                        .source_if_exists(home_path.join(".bash_profile").as_path(), &params)
615                        .await?
616                        || self
617                            .source_if_exists(home_path.join(".bash_login").as_path(), &params)
618                            .await?);
619
620                if !sourced {
621                    self.source_if_exists(home_path.join(".profile").as_path(), &params)
622                        .await?;
623                }
624            }
625        } else if self.options.interactive {
626            // --norc means skip this. Also skip in sh mode.
627            if skip_rc || self.options.sh_mode {
628                return Ok(());
629            }
630
631            // If an rc file was specified, then source it.
632            if let Some(rc_file) = rc_file {
633                // If an explicit rc file is provided, source it.
634                self.source_if_exists(rc_file, &params).await?;
635            } else {
636                //
637                // Otherwise, for non-login interactive shells, load in this order:
638                //
639                //     /etc/bash.bashrc
640                //     ~/.bashrc
641                //
642                self.source_if_exists(Path::new("/etc/bash.bashrc"), &params)
643                    .await?;
644                if let Some(home_path) = self.home_dir() {
645                    self.source_if_exists(home_path.join(".bashrc").as_path(), &params)
646                        .await?;
647                    self.source_if_exists(home_path.join(".brushrc").as_path(), &params)
648                        .await?;
649                }
650            }
651        } else {
652            let env_var_name = if self.options.sh_mode {
653                "ENV"
654            } else {
655                "BASH_ENV"
656            };
657
658            if self.env.is_set(env_var_name) {
659                //
660                // TODO: look at $ENV/BASH_ENV; source its expansion if that file exists
661                //
662                return error::unimp(
663                    "load config from $ENV/BASH_ENV for non-interactive, non-login shell",
664                );
665            }
666        }
667
668        Ok(())
669    }
670
671    async fn source_if_exists(
672        &mut self,
673        path: impl AsRef<Path>,
674        params: &ExecutionParameters,
675    ) -> Result<bool, error::Error> {
676        let path = path.as_ref();
677        if path.exists() {
678            self.source_script(path, std::iter::empty::<String>(), params)
679                .await?;
680            Ok(true)
681        } else {
682            tracing::debug!("skipping non-existent file: {}", path.display());
683            Ok(false)
684        }
685    }
686
687    /// Source the given file as a shell script, returning the execution result.
688    ///
689    /// # Arguments
690    ///
691    /// * `path` - The path to the file to source.
692    /// * `args` - The arguments to pass to the script as positional parameters.
693    /// * `params` - Execution parameters.
694    pub async fn source_script<S: AsRef<str>, P: AsRef<Path>, I: Iterator<Item = S>>(
695        &mut self,
696        path: P,
697        args: I,
698        params: &ExecutionParameters,
699    ) -> Result<ExecutionResult, error::Error> {
700        self.parse_and_execute_script_file(path.as_ref(), args, params, scripts::CallType::Sourced)
701            .await
702    }
703
704    /// Parse and execute the given file as a shell script, returning the execution result.
705    ///
706    /// # Arguments
707    ///
708    /// * `path` - The path to the file to source.
709    /// * `args` - The arguments to pass to the script as positional parameters.
710    /// * `params` - Execution parameters.
711    /// * `call_type` - The type of script call being made.
712    async fn parse_and_execute_script_file<S: AsRef<str>, P: AsRef<Path>, I: Iterator<Item = S>>(
713        &mut self,
714        path: P,
715        args: I,
716        params: &ExecutionParameters,
717        call_type: scripts::CallType,
718    ) -> Result<ExecutionResult, error::Error> {
719        let path = path.as_ref();
720        tracing::debug!("sourcing: {}", path.display());
721
722        let mut options = std::fs::File::options();
723        options.read(true);
724
725        let opened_file: openfiles::OpenFile = self
726            .open_file(&options, path, params)
727            .map_err(|e| error::ErrorKind::FailedSourcingFile(path.to_owned(), e))?;
728
729        if opened_file.is_dir() {
730            return Err(error::ErrorKind::FailedSourcingFile(
731                path.to_owned(),
732                std::io::Error::from(std::io::ErrorKind::IsADirectory),
733            )
734            .into());
735        }
736
737        let source_info = crate::parser::SourceInfo {
738            source: path.to_string_lossy().to_string(),
739        };
740
741        let mut result = self
742            .source_file(opened_file, &source_info, args, params, call_type)
743            .await?;
744
745        // Handle control flow at script execution boundary. If execution completed
746        // with a `return`, we need to clear it since it's already been "used". All
747        // other control flow types are preserved.
748        if matches!(
749            result.next_control_flow,
750            ExecutionControlFlow::ReturnFromFunctionOrScript
751        ) {
752            result.next_control_flow = ExecutionControlFlow::Normal;
753        }
754
755        Ok(result)
756    }
757
758    /// Source the given file as a shell script, returning the execution result.
759    ///
760    /// # Arguments
761    ///
762    /// * `file` - The file to source.
763    /// * `source_info` - Information about the source of the script.
764    /// * `args` - The arguments to pass to the script as positional parameters.
765    /// * `params` - Execution parameters.
766    /// * `call_type` - The type of script call being made.
767    async fn source_file<F: Read, S: AsRef<str>, I: Iterator<Item = S>>(
768        &mut self,
769        file: F,
770        source_info: &crate::parser::SourceInfo,
771        args: I,
772        params: &ExecutionParameters,
773        call_type: scripts::CallType,
774    ) -> Result<ExecutionResult, error::Error> {
775        let mut reader = std::io::BufReader::new(file);
776        let mut parser =
777            crate::parser::Parser::new(&mut reader, &self.parser_options(), source_info);
778
779        tracing::debug!(target: trace_categories::PARSE, "Parsing sourced file: {}", source_info.source);
780        let parse_result = parser.parse_program();
781
782        let mut other_positional_parameters: Vec<_> = args.map(|s| s.as_ref().to_owned()).collect();
783        let mut other_shell_name = Some(source_info.source.clone());
784        let positional_params_given = !other_positional_parameters.is_empty();
785
786        // TODO: Find a cleaner way to change args.
787        std::mem::swap(&mut self.shell_name, &mut other_shell_name);
788
789        // NOTE: We only shadow the original positional parameters if any were explicitly given
790        // for the script sourcing.
791        if positional_params_given {
792            std::mem::swap(
793                &mut self.positional_parameters,
794                &mut other_positional_parameters,
795            );
796        }
797
798        self.script_call_stack
799            .push(call_type, source_info.source.clone());
800
801        let result = self
802            .run_parsed_result(parse_result, source_info, params)
803            .await;
804
805        self.script_call_stack.pop();
806
807        // Restore.
808        std::mem::swap(&mut self.shell_name, &mut other_shell_name);
809
810        // We only restore the original positional parameters if we needed to shadow them.
811        if positional_params_given {
812            std::mem::swap(
813                &mut self.positional_parameters,
814                &mut other_positional_parameters,
815            );
816        }
817
818        result
819    }
820
821    /// Invokes a function defined in this shell, returning the resulting exit status.
822    ///
823    /// # Arguments
824    ///
825    /// * `name` - The name of the function to invoke.
826    /// * `args` - The arguments to pass to the function.
827    /// * `params` - Execution parameters to use for the invocation.
828    pub async fn invoke_function<N: AsRef<str>, I: IntoIterator<Item = A>, A: AsRef<str>>(
829        &mut self,
830        name: N,
831        args: I,
832        params: &ExecutionParameters,
833    ) -> Result<u8, error::Error> {
834        let name = name.as_ref();
835        let command_name = String::from(name);
836
837        let func_registration = self
838            .funcs
839            .get(name)
840            .ok_or_else(|| error::ErrorKind::FunctionNotFound(name.to_owned()))?;
841
842        let func = func_registration.definition.clone();
843
844        let context = commands::ExecutionContext {
845            shell: self,
846            command_name,
847            params: params.clone(),
848        };
849
850        let command_args = args
851            .into_iter()
852            .map(|s| commands::CommandArg::String(String::from(s.as_ref())))
853            .collect::<Vec<_>>();
854
855        match commands::invoke_shell_function(func, context, &command_args).await? {
856            ExecutionSpawnResult::StartedProcess(_) => {
857                error::unimp("child spawned from function invocation")
858            }
859            ExecutionSpawnResult::Completed(result) => Ok(result.exit_code.into()),
860        }
861    }
862
863    /// Executes the given string as a shell program, returning the resulting exit status.
864    ///
865    /// # Arguments
866    ///
867    /// * `command` - The command to execute.
868    /// * `params` - Execution parameters.
869    pub async fn exec<S: Into<String>>(
870        &mut self,
871        command: S,
872        params: &ExecutionParameters,
873    ) -> Result<ExecutionResult, error::Error> {
874        // TODO: Actually track line numbers; this is something of a hack, assuming each time
875        // this function is invoked we are on the next line of the input. For one thing,
876        // each string we run could be multiple lines.
877        self.current_line_number += 1;
878
879        let parse_result = self.parse_string(command.into());
880        let source_info = crate::parser::SourceInfo {
881            source: String::from("main"),
882        };
883        self.run_parsed_result(parse_result, &source_info, params)
884            .await
885    }
886
887    /// Executes command with streaming I/O.
888    ///
889    /// Returns:
890    /// - `impl Stream<Item = StreamingOutput>` - stdout/stderr chunks as they arrive
891    /// - `Sender<Vec<u8>>` - channel to write to stdin (drop to close stdin)
892    ///
893    /// Cancellable via CancellationToken in ExecutionParameters.
894    pub fn stream<S: Into<String>>(
895        &mut self,
896        command: S,
897        params: &ExecutionParameters,
898    ) -> Result<(
899        tokio_stream::wrappers::ReceiverStream<super::results::StreamingOutput>,
900        tokio::sync::mpsc::Sender<Vec<u8>>,
901    ), error::Error> {
902        use std::io::{Read, Write};
903        use super::results::StreamingOutput;
904
905        let command = command.into();
906
907        // Parse upfront - fail fast on syntax errors
908        let program = self.parse_string(&command).map_err(|e| {
909            error::Error::from(error::ErrorKind::ParseError(
910                e,
911                crate::parser::SourceInfo { source: String::from("streaming") },
912            ))
913        })?;
914
915        // Create pipes for stdin, stdout, stderr
916        let (stdin_reader, stdin_writer) =
917            std::io::pipe().map_err(|e| error::Error::from(error::ErrorKind::IoError(e)))?;
918        let (stdout_reader, stdout_writer) =
919            std::io::pipe().map_err(|e| error::Error::from(error::ErrorKind::IoError(e)))?;
920        let (stderr_reader, stderr_writer) =
921            std::io::pipe().map_err(|e| error::Error::from(error::ErrorKind::IoError(e)))?;
922
923        // Channel for output chunks
924        let (output_tx, output_rx) = tokio::sync::mpsc::channel::<StreamingOutput>(64);
925
926        // Channel for stdin input
927        let (stdin_tx, mut stdin_rx) = tokio::sync::mpsc::channel::<Vec<u8>>(64);
928
929        // Subshell with pipes attached
930        let mut subshell = self.clone();
931        let mut exec_params = params.clone();
932        exec_params.set_fd(openfiles::OpenFiles::STDIN_FD, openfiles::OpenFile::PipeReader(stdin_reader));
933        exec_params.set_fd(openfiles::OpenFiles::STDOUT_FD, openfiles::OpenFile::PipeWriter(stdout_writer));
934        exec_params.set_fd(openfiles::OpenFiles::STDERR_FD, openfiles::OpenFile::PipeWriter(stderr_writer));
935
936        // Spawn execution (pipe writers drop when done → EOF to readers)
937        tokio::spawn(async move {
938            let _ = subshell.run_program(program, &exec_params).await;
939            // exec_params dropped here, pipe writers closed
940        });
941
942        // Stdin writer task - forwards channel data to pipe
943        tokio::task::spawn_blocking(move || {
944            let mut w = stdin_writer;
945            while let Some(data) = stdin_rx.blocking_recv() {
946                if w.write_all(&data).is_err() {
947                    break;
948                }
949            }
950            // stdin_writer dropped here, signals EOF to process
951        });
952
953        // Stdout reader
954        let tx1 = output_tx.clone();
955        tokio::task::spawn_blocking(move || {
956            let mut buf = [0u8; 4096];
957            let mut r = stdout_reader;
958            loop {
959                match r.read(&mut buf) {
960                    Ok(0) => break,
961                    Ok(n) => { let _ = tx1.blocking_send(StreamingOutput::stdout(buf[..n].to_vec())); }
962                    Err(e) if e.kind() == std::io::ErrorKind::Interrupted => continue,
963                    Err(_) => break,
964                }
965            }
966        });
967
968        // Stderr reader
969        tokio::task::spawn_blocking(move || {
970            let mut buf = [0u8; 4096];
971            let mut r = stderr_reader;
972            loop {
973                match r.read(&mut buf) {
974                    Ok(0) => break,
975                    Ok(n) => { let _ = output_tx.blocking_send(StreamingOutput::stderr(buf[..n].to_vec())); }
976                    Err(e) if e.kind() == std::io::ErrorKind::Interrupted => continue,
977                    Err(_) => break,
978                }
979            }
980        });
981
982        Ok((tokio_stream::wrappers::ReceiverStream::new(output_rx), stdin_tx))
983    }
984
985    /// Parses the given reader as a shell program, returning the resulting Abstract Syntax Tree
986    /// for the program.
987    pub fn parse<R: Read>(
988        &self,
989        reader: R,
990    ) -> Result<crate::parser::ast::Program, crate::parser::ParseError> {
991        let mut parser = create_parser(reader, &self.parser_options());
992
993        tracing::debug!(target: trace_categories::PARSE, "Parsing reader as program...");
994        parser.parse_program()
995    }
996
997    /// Parses the given string as a shell program, returning the resulting Abstract Syntax Tree
998    /// for the program.
999    ///
1000    /// # Arguments
1001    ///
1002    /// * `s` - The string to parse as a program.
1003    pub fn parse_string<S: Into<String>>(
1004        &self,
1005        s: S,
1006    ) -> Result<crate::parser::ast::Program, crate::parser::ParseError> {
1007        parse_string_impl(s.into(), self.parser_options())
1008    }
1009
1010    /// Applies basic shell expansion to the provided string.
1011    ///
1012    /// # Arguments
1013    ///
1014    /// * `s` - The string to expand.
1015    pub async fn basic_expand_string<S: AsRef<str>>(
1016        &mut self,
1017        params: &ExecutionParameters,
1018        s: S,
1019    ) -> Result<String, error::Error> {
1020        let result = expansion::basic_expand_str(self, params, s.as_ref()).await?;
1021        Ok(result)
1022    }
1023
1024    /// Applies full shell expansion and field splitting to the provided string; returns
1025    /// a sequence of fields.
1026    ///
1027    /// # Arguments
1028    ///
1029    /// * `s` - The string to expand and split.
1030    pub async fn full_expand_and_split_string<S: AsRef<str>>(
1031        &mut self,
1032        params: &ExecutionParameters,
1033        s: S,
1034    ) -> Result<Vec<String>, error::Error> {
1035        let result = expansion::full_expand_and_split_str(self, params, s.as_ref()).await?;
1036        Ok(result)
1037    }
1038
1039    /// Returns the default execution parameters for this shell.
1040    pub fn default_exec_params(&self) -> ExecutionParameters {
1041        ExecutionParameters::default()
1042    }
1043
1044    /// Executes the given script file, returning the resulting exit status.
1045    ///
1046    /// # Arguments
1047    ///
1048    /// * `script_path` - The path to the script file to execute.
1049    /// * `args` - The arguments to pass to the script as positional parameters.
1050    pub async fn run_script<S: AsRef<str>, P: AsRef<Path>, I: Iterator<Item = S>>(
1051        &mut self,
1052        script_path: P,
1053        args: I,
1054    ) -> Result<ExecutionResult, error::Error> {
1055        let params = self.default_exec_params();
1056        let result = self
1057            .parse_and_execute_script_file(
1058                script_path.as_ref(),
1059                args,
1060                &params,
1061                scripts::CallType::Executed,
1062            )
1063            .await?;
1064
1065        let _ = self.on_exit().await;
1066
1067        Ok(result)
1068    }
1069
1070    /// Runs any exit steps for the shell.
1071    pub async fn on_exit(&mut self) -> Result<(), error::Error> {
1072        self.invoke_exit_trap_handler_if_registered().await?;
1073
1074        Ok(())
1075    }
1076
1077    async fn invoke_exit_trap_handler_if_registered(
1078        &mut self,
1079    ) -> Result<ExecutionResult, error::Error> {
1080        let Some(handler) = self.traps.handlers.get(&traps::TrapSignal::Exit).cloned() else {
1081            return Ok(ExecutionResult::success());
1082        };
1083
1084        // TODO: Confirm whether trap handlers should be executed in the same process group.
1085        let mut params = self.default_exec_params();
1086        params.process_group_policy = ProcessGroupPolicy::SameProcessGroup;
1087
1088        let orig_last_exit_status = self.last_exit_status;
1089        self.traps.handler_depth += 1;
1090
1091        let result = self.exec(handler, &params).await;
1092
1093        self.traps.handler_depth -= 1;
1094        self.last_exit_status = orig_last_exit_status;
1095
1096        result
1097    }
1098
1099    pub(crate) async fn run_parsed_result(
1100        &mut self,
1101        parse_result: Result<crate::parser::ast::Program, crate::parser::ParseError>,
1102        source_info: &crate::parser::SourceInfo,
1103        params: &ExecutionParameters,
1104    ) -> Result<ExecutionResult, error::Error> {
1105        // If parsing succeeded, run the program. If there's a parse error, it's fatal (per spec).
1106        let result = match parse_result {
1107            Ok(prog) => self.run_program(prog, params).await,
1108            Err(parse_err) => Err(error::Error::from(error::ErrorKind::ParseError(
1109                parse_err,
1110                source_info.clone(),
1111            ))
1112            .into_fatal()),
1113        };
1114
1115        // Report any errors.
1116        match result {
1117            Ok(result) => Ok(result),
1118            Err(err) => {
1119                let _ = self.display_error(&mut params.stderr(self), &err).await;
1120
1121                let result = err.into_result(self);
1122                *self.last_exit_status_mut() = result.exit_code.into();
1123
1124                Ok(result)
1125            }
1126        }
1127    }
1128
1129    /// Executes the given parsed shell program, returning the resulting exit status.
1130    ///
1131    /// # Arguments
1132    ///
1133    /// * `program` - The program to execute.
1134    /// * `params` - Execution parameters.
1135    pub async fn run_program(
1136        &mut self,
1137        program: crate::parser::ast::Program,
1138        params: &ExecutionParameters,
1139    ) -> Result<ExecutionResult, error::Error> {
1140        program.execute(self, params).await
1141    }
1142
1143    const fn default_prompt(&self) -> &'static str {
1144        if self.options.sh_mode {
1145            "$ "
1146        } else {
1147            "brush$ "
1148        }
1149    }
1150
1151    /// Composes the shell's post-input, pre-command prompt, applying all appropriate expansions.
1152    pub async fn compose_precmd_prompt(&mut self) -> Result<String, error::Error> {
1153        self.expand_prompt_var("PS0", "").await
1154    }
1155
1156    /// Composes the shell's prompt, applying all appropriate expansions.
1157    pub async fn compose_prompt(&mut self) -> Result<String, error::Error> {
1158        self.expand_prompt_var("PS1", self.default_prompt()).await
1159    }
1160
1161    /// Compose's the shell's alternate-side prompt, applying all appropriate expansions.
1162    pub async fn compose_alt_side_prompt(&mut self) -> Result<String, error::Error> {
1163        // This is a brush extension.
1164        self.expand_prompt_var("BRUSH_PS_ALT", "").await
1165    }
1166
1167    /// Composes the shell's continuation prompt.
1168    pub async fn compose_continuation_prompt(&mut self) -> Result<String, error::Error> {
1169        self.expand_prompt_var("PS2", "> ").await
1170    }
1171
1172    async fn expand_prompt_var(
1173        &mut self,
1174        var_name: &str,
1175        default: &str,
1176    ) -> Result<String, error::Error> {
1177        //
1178        // TODO(prompt): bash appears to do this in a subshell; we need to investigate
1179        // if that's required.
1180        //
1181
1182        // Retrieve the spec.
1183        let prompt_spec = self.parameter_or_default(var_name, default);
1184        if prompt_spec.is_empty() {
1185            return Ok(String::new());
1186        }
1187
1188        // Expand it.
1189        let params = self.default_exec_params();
1190        prompt::expand_prompt(self, &params, prompt_spec.into_owned()).await
1191    }
1192
1193    fn parameter_or_default<'a>(&'a self, name: &str, default: &'a str) -> Cow<'a, str> {
1194        self.env_str(name).unwrap_or_else(|| default.into())
1195    }
1196
1197    /// Returns the options that should be used for parsing shell programs; reflects
1198    /// the current configuration state of the shell and may change over time.
1199    pub const fn parser_options(&self) -> crate::parser::ParserOptions {
1200        crate::parser::ParserOptions {
1201            enable_extended_globbing: self.options.extended_globbing,
1202            posix_mode: self.options.posix_mode,
1203            sh_mode: self.options.sh_mode,
1204            tilde_expansion: true,
1205        }
1206    }
1207
1208    /// Returns whether or not the shell is actively executing in a sourced script.
1209    pub fn in_sourced_script(&self) -> bool {
1210        self.script_call_stack.in_sourced_script()
1211    }
1212
1213    /// Returns whether or not the shell is actively executing in a shell function.
1214    pub fn in_function(&self) -> bool {
1215        !self.function_call_stack.is_empty()
1216    }
1217
1218    /// Updates the shell's internal tracking state to reflect that a new shell
1219    /// function is being entered.
1220    ///
1221    /// # Arguments
1222    ///
1223    /// * `name` - The name of the function being entered.
1224    /// * `function_def` - The definition of the function being entered.
1225    pub(crate) fn enter_function(
1226        &mut self,
1227        name: &str,
1228        function_def: &Arc<crate::parser::ast::FunctionDefinition>,
1229    ) -> Result<(), error::Error> {
1230        if let Some(max_call_depth) = self.options.max_function_call_depth
1231            && self.function_call_stack.depth() >= max_call_depth {
1232                return Err(error::ErrorKind::MaxFunctionCallDepthExceeded.into());
1233            }
1234
1235        if tracing::enabled!(target: trace_categories::FUNCTIONS, tracing::Level::DEBUG) {
1236            let depth = self.function_call_stack.depth();
1237            let prefix = repeated_char_str(' ', depth);
1238            tracing::debug!(target: trace_categories::FUNCTIONS, "Entering func [depth={depth}]: {prefix}{name}");
1239        }
1240
1241        self.function_call_stack.push(name, function_def);
1242        self.env.push_scope(env::EnvironmentScope::Local);
1243
1244        Ok(())
1245    }
1246
1247    /// Updates the shell's internal tracking state to reflect that the shell
1248    /// has exited the top-most function on its call stack.
1249    pub(crate) fn leave_function(&mut self) -> Result<(), error::Error> {
1250        self.env.pop_scope(env::EnvironmentScope::Local)?;
1251
1252        if let Some(exited_call) = self.function_call_stack.pop()
1253            && tracing::enabled!(target: trace_categories::FUNCTIONS, tracing::Level::DEBUG) {
1254                let depth = self.function_call_stack.depth();
1255                let prefix = repeated_char_str(' ', depth);
1256                tracing::debug!(target: trace_categories::FUNCTIONS, "Exiting func  [depth={depth}]: {prefix}{}", exited_call.function_name);
1257            }
1258
1259        Ok(())
1260    }
1261
1262    /// Returns the path to the history file used by the shell, if one is set.
1263    pub fn history_file_path(&self) -> Option<PathBuf> {
1264        self.env_str("HISTFILE")
1265            .map(|s| PathBuf::from(s.into_owned()))
1266    }
1267
1268    /// Returns the path to the history file used by the shell, if one is set.
1269    pub fn history_time_format(&self) -> Option<String> {
1270        self.env_str("HISTTIMEFORMAT").map(|s| s.into_owned())
1271    }
1272
1273    /// Saves history back to any backing storage.
1274    pub fn save_history(&mut self) -> Result<(), error::Error> {
1275        if let Some(history_file_path) = self.history_file_path()
1276            && let Some(history) = &mut self.history {
1277                // See if there's *any* time format configured. That triggers writing out timestamps.
1278                let write_timestamps = self.env.is_set("HISTTIMEFORMAT");
1279
1280                // TODO: Observe options.append_to_history_file
1281                history.flush(
1282                    history_file_path,
1283                    true, /*append?*/
1284                    true, /*unsaved items only?*/
1285                    write_timestamps,
1286                )?;
1287            }
1288
1289        Ok(())
1290    }
1291
1292    /// Adds a command to history.
1293    pub fn add_to_history(&mut self, command: &str) -> Result<(), error::Error> {
1294        if let Some(history) = &mut self.history {
1295            // Trim.
1296            let command = command.trim();
1297
1298            // For now, discard empty commands.
1299            if command.is_empty() {
1300                return Ok(());
1301            }
1302
1303            // Add it to history.
1304            history.add(history::Item {
1305                id: 0,
1306                command_line: command.to_owned(),
1307                timestamp: Some(chrono::Utc::now()),
1308                dirty: true,
1309            })?;
1310        }
1311
1312        Ok(())
1313    }
1314
1315    /// Tries to retrieve a variable from the shell's environment, converting it into its
1316    /// string form.
1317    ///
1318    /// # Arguments
1319    ///
1320    /// * `name` - The name of the variable to retrieve.
1321    pub fn env_str(&self, name: &str) -> Option<Cow<'_, str>> {
1322        self.env.get_str(name, self)
1323    }
1324
1325    /// Tries to retrieve a variable from the shell's environment.
1326    ///
1327    /// # Arguments
1328    ///
1329    /// * `name` - The name of the variable to retrieve.
1330    pub fn env_var(&self, name: &str) -> Option<&ShellVariable> {
1331        self.env.get(name).map(|(_, var)| var)
1332    }
1333
1334    /// Tries to set a global variable in the shell's environment.
1335    ///
1336    /// # Arguments
1337    ///
1338    /// * `name` - The name of the variable to add.
1339    /// * `var` - The variable contents to add.
1340    pub fn set_env_global(&mut self, name: &str, var: ShellVariable) -> Result<(), error::Error> {
1341        self.env.set_global(name, var)
1342    }
1343
1344    /// Register a builtin to the shell's environment.
1345    ///
1346    /// # Arguments
1347    ///
1348    /// * `name` - The in-shell name of the builtin.
1349    /// * `registration` - The registration handle for the builtin.
1350    pub fn register_builtin<S: Into<String>>(
1351        &mut self,
1352        name: S,
1353        registration: builtins::Registration,
1354    ) {
1355        self.builtins.insert(name.into(), registration);
1356    }
1357
1358    /// Tries to retrieve a mutable reference to an existing builtin registration.
1359    /// Returns `None` if no such registration exists.
1360    ///
1361    /// # Arguments
1362    ///
1363    /// * `name` - The name of the builtin to lookup.
1364    pub fn builtin_mut(&mut self, name: &str) -> Option<&mut builtins::Registration> {
1365        self.builtins.get_mut(name)
1366    }
1367
1368    /// Returns the current value of the IFS variable, or the default value if it is not set.
1369    pub fn ifs(&self) -> Cow<'_, str> {
1370        self.env_str("IFS").unwrap_or_else(|| " \t\n".into())
1371    }
1372
1373    /// Returns the first character of the IFS variable, or a space if it is not set.
1374    pub(crate) fn get_ifs_first_char(&self) -> char {
1375        self.ifs().chars().next().unwrap_or(' ')
1376    }
1377
1378    /// Generates command completions for the shell.
1379    ///
1380    /// # Arguments
1381    ///
1382    /// * `input` - The input string to generate completions for.
1383    /// * `position` - The position in the input string to generate completions at.
1384    pub async fn complete(
1385        &mut self,
1386        input: &str,
1387        position: usize,
1388    ) -> Result<completion::Completions, error::Error> {
1389        let completion_config = self.completion_config.clone();
1390        completion_config
1391            .get_completions(self, input, position)
1392            .await
1393    }
1394
1395    /// Finds executables in the shell's current default PATH, matching the given glob pattern.
1396    ///
1397    /// # Arguments
1398    ///
1399    /// * `required_glob_pattern` - The glob pattern to match against.
1400    pub fn find_executables_in_path<'a>(
1401        &'a self,
1402        filename: &'a str,
1403    ) -> impl Iterator<Item = PathBuf> + 'a {
1404        let path_var = self.env.get_str("PATH", self).unwrap_or_default();
1405        let paths = path_var.split(':').map(|s| s.to_owned());
1406
1407        pathsearch::search_for_executable(paths.into_iter(), filename)
1408    }
1409
1410    /// Finds executables in the shell's current default PATH, with filenames matching the
1411    /// given prefix.
1412    ///
1413    /// # Arguments
1414    ///
1415    /// * `filename_prefix` - The prefix to match against executable filenames.
1416    pub fn find_executables_in_path_with_prefix(
1417        &self,
1418        filename_prefix: &str,
1419        case_insensitive: bool,
1420    ) -> impl Iterator<Item = PathBuf> {
1421        let path_var = self.env.get_str("PATH", self).unwrap_or_default();
1422        let paths = path_var.split(':').map(|s| s.to_owned());
1423
1424        pathsearch::search_for_executable_with_prefix(
1425            paths.into_iter(),
1426            filename_prefix,
1427            case_insensitive,
1428        )
1429    }
1430
1431    /// Determines whether the given filename is the name of an executable in one of the
1432    /// directories in the shell's current PATH. If found, returns the path.
1433    ///
1434    /// # Arguments
1435    ///
1436    /// * `candidate_name` - The name of the file to look for.
1437    pub fn find_first_executable_in_path<S: AsRef<str>>(
1438        &self,
1439        candidate_name: S,
1440    ) -> Option<PathBuf> {
1441        for dir_str in self.env_str("PATH").unwrap_or_default().split(':') {
1442            let candidate_path = Path::new(dir_str).join(candidate_name.as_ref());
1443            if candidate_path.executable() {
1444                return Some(candidate_path);
1445            }
1446        }
1447        None
1448    }
1449
1450    /// Uses the shell's hash-based path cache to check whether the given filename is the name
1451    /// of an executable in one of the directories in the shell's current PATH. If found,
1452    /// ensures the path is in the cache and returns it.
1453    ///
1454    /// # Arguments
1455    ///
1456    /// * `candidate_name` - The name of the file to look for.
1457    pub fn find_first_executable_in_path_using_cache<S: AsRef<str>>(
1458        &mut self,
1459        candidate_name: S,
1460    ) -> Option<PathBuf> {
1461        if let Some(cached_path) = self.program_location_cache.get(&candidate_name) {
1462            Some(cached_path)
1463        } else if let Some(found_path) = self.find_first_executable_in_path(&candidate_name) {
1464            self.program_location_cache
1465                .set(&candidate_name, found_path.clone());
1466            Some(found_path)
1467        } else {
1468            None
1469        }
1470    }
1471
1472    /// Gets the absolute form of the given path.
1473    ///
1474    /// # Arguments
1475    ///
1476    /// * `path` - The path to get the absolute form of.
1477    pub fn absolute_path(&self, path: impl AsRef<Path>) -> PathBuf {
1478        let path = path.as_ref();
1479        if path.as_os_str().is_empty() || path.is_absolute() {
1480            path.to_owned()
1481        } else {
1482            self.working_dir().join(path)
1483        }
1484    }
1485
1486    /// Opens the given file, using the context of this shell and the provided execution parameters.
1487    ///
1488    /// # Arguments
1489    ///
1490    /// * `options` - The options to use opening the file.
1491    /// * `path` - The path to the file to open; may be relative to the shell's working directory.
1492    /// * `params` - Execution parameters.
1493    pub(crate) fn open_file(
1494        &self,
1495        options: &std::fs::OpenOptions,
1496        path: impl AsRef<Path>,
1497        params: &ExecutionParameters,
1498    ) -> Result<openfiles::OpenFile, std::io::Error> {
1499        let path_to_open = self.absolute_path(path.as_ref());
1500
1501        // See if this is a reference to a file descriptor, in which case the actual
1502        // /dev/fd* file path for this process may not match with what's in the execution
1503        // parameters.
1504        if let Some(parent) = path_to_open.parent()
1505            && parent == Path::new("/dev/fd")
1506                && let Some(filename) = path_to_open.file_name()
1507                    && let Ok(fd_num) = filename.to_string_lossy().to_string().parse::<ShellFd>()
1508                        && let Some(open_file) = params.try_fd(self, fd_num) {
1509                            return open_file.try_clone();
1510                        }
1511
1512        Ok(options.open(path_to_open)?.into())
1513    }
1514
1515    /// Sets the shell's current working directory to the given path.
1516    ///
1517    /// # Arguments
1518    ///
1519    /// * `target_dir` - The path to set as the working directory.
1520    pub fn set_working_dir(&mut self, target_dir: impl AsRef<Path>) -> Result<(), error::Error> {
1521        let abs_path = self.absolute_path(target_dir.as_ref());
1522
1523        match std::fs::metadata(&abs_path) {
1524            Ok(m) => {
1525                if !m.is_dir() {
1526                    return Err(error::ErrorKind::NotADirectory(abs_path).into());
1527                }
1528            }
1529            Err(e) => {
1530                return Err(e.into());
1531            }
1532        }
1533
1534        // Normalize the path (but don't canonicalize it).
1535        let cleaned_path = abs_path.normalize();
1536
1537        let pwd = cleaned_path.to_string_lossy().to_string();
1538
1539        self.env.update_or_add(
1540            "PWD",
1541            variables::ShellValueLiteral::Scalar(pwd),
1542            |var| {
1543                var.export();
1544                Ok(())
1545            },
1546            EnvironmentLookup::Anywhere,
1547            EnvironmentScope::Global,
1548        )?;
1549        let oldpwd = std::mem::replace(self.working_dir_mut(), cleaned_path);
1550
1551        self.env.update_or_add(
1552            "OLDPWD",
1553            variables::ShellValueLiteral::Scalar(oldpwd.to_string_lossy().to_string()),
1554            |var| {
1555                var.export();
1556                Ok(())
1557            },
1558            EnvironmentLookup::Anywhere,
1559            EnvironmentScope::Global,
1560        )?;
1561
1562        Ok(())
1563    }
1564
1565    /// Tilde-shortens the given string, replacing the user's home directory with a tilde.
1566    ///
1567    /// # Arguments
1568    ///
1569    /// * `s` - The string to shorten.
1570    pub fn tilde_shorten(&self, s: String) -> String {
1571        if let Some(home_dir) = self.home_dir()
1572            && let Some(stripped) = s.strip_prefix(home_dir.to_string_lossy().as_ref()) {
1573                return format!("~{stripped}");
1574            }
1575        s
1576    }
1577
1578    /// Returns the shell's current home directory, if available.
1579    pub(crate) fn home_dir(&self) -> Option<PathBuf> {
1580        if let Some(home) = self.env.get_str("HOME", self) {
1581            Some(PathBuf::from(home.to_string()))
1582        } else {
1583            // HOME isn't set, so let's sort it out ourselves.
1584            users::get_current_user_home_dir()
1585        }
1586    }
1587
1588    /// Replaces the shell's currently configured open files with the given set.
1589    /// Typically only used by exec-like builtins.
1590    ///
1591    /// # Arguments
1592    ///
1593    /// * `open_files` - The new set of open files to use.
1594    pub fn replace_open_files(
1595        &mut self,
1596        open_fds: impl Iterator<Item = (ShellFd, openfiles::OpenFile)>,
1597    ) {
1598        self.open_files = openfiles::OpenFiles::from(open_fds);
1599    }
1600
1601    pub(crate) const fn persistent_open_files(&self) -> &openfiles::OpenFiles {
1602        &self.open_files
1603    }
1604
1605    /// Returns a value that can be used to write to the shell's currently configured
1606    /// standard output stream using `write!` at al.
1607    pub fn stdout(&self) -> impl std::io::Write {
1608        self.open_files.try_stdout().cloned().unwrap()
1609    }
1610
1611    /// Returns a value that can be used to write to the shell's currently configured
1612    /// standard error stream using `write!` et al.
1613    pub fn stderr(&self) -> impl std::io::Write {
1614        self.open_files.try_stderr().cloned().unwrap()
1615    }
1616
1617    /// Outputs `set -x` style trace output for a command.
1618    ///
1619    /// # Arguments
1620    ///
1621    /// * `command` - The command to trace.
1622    pub(crate) async fn trace_command<S: AsRef<str>>(
1623        &mut self,
1624        params: &ExecutionParameters,
1625        command: S,
1626    ) -> Result<(), error::Error> {
1627        // Expand the PS4 prompt variable to get our prefix.
1628        let ps4 = self.as_mut().expand_prompt_var("PS4", "").await?;
1629        let mut prefix = ps4;
1630
1631        // Add additional depth-based prefixes using the first character of PS4.
1632        let additional_depth = self.script_call_stack.depth() + self.depth;
1633        if let Some(c) = prefix.chars().next() {
1634            for _ in 0..additional_depth {
1635                prefix.insert(0, c);
1636            }
1637        }
1638
1639        // Resolve which file descriptor to use for tracing. We default to stderr.
1640        let mut trace_file = params.try_stderr(self);
1641
1642        // If BASH_XTRACEFD is set and refers to a valid file descriptor, use that instead.
1643        if let Some((_, xtracefd_var)) = self.env.get("BASH_XTRACEFD") {
1644            let xtracefd_value = xtracefd_var.value().to_cow_str(self);
1645            if let Ok(fd) = xtracefd_value.parse::<ShellFd>()
1646                && let Some(file) = self.open_files.try_fd(fd) {
1647                    trace_file = Some(file.clone());
1648                }
1649        }
1650
1651        // If we have a valid trace file, write to it.
1652        if let Some(trace_file) = trace_file {
1653            let mut trace_file = trace_file.try_clone()?;
1654            writeln!(trace_file, "{prefix}{}", command.as_ref())?;
1655        }
1656
1657        Ok(())
1658    }
1659
1660    /// Returns the keywords that are reserved by the shell.
1661    pub(crate) fn get_keywords(&self) -> Vec<String> {
1662        if self.options.sh_mode {
1663            keywords::SH_MODE_KEYWORDS.iter().cloned().collect()
1664        } else {
1665            keywords::KEYWORDS.iter().cloned().collect()
1666        }
1667    }
1668
1669    /// Checks if the given string is a keyword reserved in this shell.
1670    ///
1671    /// # Arguments
1672    ///
1673    /// * `s` - The string to check.
1674    pub fn is_keyword(&self, s: &str) -> bool {
1675        if self.options.sh_mode {
1676            keywords::SH_MODE_KEYWORDS.contains(s)
1677        } else {
1678            keywords::KEYWORDS.contains(s)
1679        }
1680    }
1681
1682    /// Checks for completed jobs in the shell, reporting any changes found.
1683    pub fn check_for_completed_jobs(&mut self) -> Result<(), error::Error> {
1684        let results = self.jobs.poll()?;
1685
1686        if self.options.enable_job_control {
1687            for (job, _result) in results {
1688                writeln!(self.stderr(), "{job}")?;
1689            }
1690        }
1691
1692        Ok(())
1693    }
1694
1695    /// Evaluate the given arithmetic expression, returning the result.
1696    pub fn eval_arithmetic(
1697        &mut self,
1698        expr: &crate::parser::ast::ArithmeticExpr,
1699    ) -> Result<i64, error::Error> {
1700        Ok(expr.eval(self)?)
1701    }
1702
1703    /// Updates the shell state to reflect the given edit buffer contents.
1704    ///
1705    /// # Arguments
1706    ///
1707    /// * `contents` - The contents of the edit buffer.
1708    /// * `cursor` - The cursor position in the edit buffer.
1709    pub fn set_edit_buffer(&mut self, contents: String, cursor: usize) -> Result<(), error::Error> {
1710        self.env
1711            .set_global("READLINE_LINE", ShellVariable::new(contents))?;
1712
1713        self.env
1714            .set_global("READLINE_POINT", ShellVariable::new(cursor.to_string()))?;
1715
1716        Ok(())
1717    }
1718
1719    /// Returns the contents of the shell's edit buffer, if any. The buffer
1720    /// state is cleared from the shell.
1721    pub fn pop_edit_buffer(&mut self) -> Result<Option<(String, usize)>, error::Error> {
1722        let line = self
1723            .env
1724            .unset("READLINE_LINE")?
1725            .map(|line| line.value().to_cow_str(self).to_string());
1726
1727        let point = self
1728            .env
1729            .unset("READLINE_POINT")?
1730            .and_then(|point| point.value().to_cow_str(self).parse::<usize>().ok())
1731            .unwrap_or(0);
1732
1733        if let Some(line) = line {
1734            Ok(Some((line, point)))
1735        } else {
1736            Ok(None)
1737        }
1738    }
1739
1740    /// Returns the shell's history, if it exists.
1741    pub const fn history(&self) -> Option<&history::History> {
1742        self.history.as_ref()
1743    }
1744
1745    /// Returns a mutable reference to the shell's history, if it exists.
1746    pub const fn history_mut(&mut self) -> Option<&mut history::History> {
1747        self.history.as_mut()
1748    }
1749
1750    /// Returns whether or not this shell is a subshell.
1751    pub const fn is_subshell(&self) -> bool {
1752        self.depth > 0
1753    }
1754
1755    /// Returns the current subshell depth; 0 is returned if this shell is not a subshell.
1756    pub const fn depth(&self) -> usize {
1757        self.depth
1758    }
1759
1760    /// Displays the given error to the user, using the shell's error display mechanisms.
1761    ///
1762    /// # Arguments
1763    ///
1764    /// * `file_table` - The open file table to use for any file descriptor references.
1765    /// * `err` - The error to display.
1766    pub async fn display_error(
1767        &self,
1768        file: &mut impl std::io::Write,
1769        err: &error::Error,
1770    ) -> Result<(), error::Error> {
1771        let str = self.error_formatter.lock().await.format_error(err, self);
1772        write!(file, "{str}")?;
1773
1774        Ok(())
1775    }
1776}
1777
1778#[cached::proc_macro::cached(size = 64, result = true)]
1779fn parse_string_impl(
1780    s: String,
1781    parser_options: crate::parser::ParserOptions,
1782) -> Result<crate::parser::ast::Program, crate::parser::ParseError> {
1783    let mut parser = create_parser(s.as_bytes(), &parser_options);
1784
1785    tracing::debug!(target: trace_categories::PARSE, "Parsing string as program...");
1786    parser.parse_program()
1787}
1788
1789fn create_parser<R: Read>(
1790    r: R,
1791    parser_options: &crate::parser::ParserOptions,
1792) -> crate::parser::Parser<std::io::BufReader<R>> {
1793    let reader = std::io::BufReader::new(r);
1794    let source_info = crate::parser::SourceInfo {
1795        source: String::from("main"),
1796    };
1797
1798    crate::parser::Parser::new(reader, parser_options, &source_info)
1799}
1800
1801fn repeated_char_str(c: char, count: usize) -> String {
1802    (0..count).map(|_| c).collect()
1803}