1use std::borrow::Cow;
4use std::collections::HashMap;
5use std::path::{Path, PathBuf};
6use std::sync::Arc;
7
8use tokio::sync::Mutex;
9
10use crate::env::ShellEnvironment;
11use crate::options::RuntimeOptions;
12use crate::{
13 ExecutionControlFlow, ExecutionResult, builtins, error, extensions, functions, interfaces,
14 jobs, keywords, openfiles, pathcache, wellknownvars,
15};
16
17pub type KeyBindingsHelper = Arc<Mutex<dyn interfaces::KeyBindings>>;
19
20pub type ShellFd = i32;
22
23mod builder;
31mod builtin_registry;
32mod callstack;
33mod completion;
34mod env;
35mod execution;
36mod expansion;
37mod fs;
38mod funcs;
39mod history;
40mod initscripts;
41mod io;
42mod job_control;
43mod parsing;
44mod prompts;
45mod readline;
46mod state;
47mod traps;
48
49pub use builder::{CreateOptions, ShellBuilder, ShellBuilderState};
50pub use initscripts::{ProfileLoadBehavior, RcLoadBehavior};
51pub use state::ShellState;
52
53#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
62pub struct Shell<SE: extensions::ShellExtensions = extensions::DefaultShellExtensions> {
63 #[cfg_attr(feature = "serde", serde(skip, default = "default_error_formatter"))]
65 error_formatter: SE::ErrorFormatter,
66
67 traps: crate::traps::TrapHandlerConfig,
69
70 open_files: openfiles::OpenFiles,
72
73 working_dir: PathBuf,
75
76 env: ShellEnvironment,
78
79 funcs: functions::FunctionEnv,
81
82 options: RuntimeOptions,
84
85 #[cfg_attr(feature = "serde", serde(skip))]
88 jobs: jobs::JobManager,
89
90 aliases: HashMap<String, String>,
92
93 last_exit_status: u8,
95
96 last_exit_status_change_count: usize,
100
101 last_pipeline_statuses: Vec<u8>,
103
104 depth: usize,
106
107 name: Option<String>,
109
110 args: Vec<String>,
112
113 version: Option<String>,
115
116 product_display_str: Option<String>,
118
119 call_stack: crate::callstack::CallStack,
121
122 directory_stack: Vec<PathBuf>,
124
125 completion: crate::completion::State,
127
128 #[cfg_attr(feature = "serde", serde(skip))]
130 builtins: HashMap<String, builtins::Registration<SE>>,
131
132 program_location_cache: pathcache::PathCache,
134
135 last_stopwatch_time: std::time::SystemTime,
137
138 last_stopwatch_offset: u32,
140
141 #[cfg_attr(feature = "serde", serde(skip))]
143 parser_impl: crate::parser::ParserImpl,
144
145 #[cfg_attr(feature = "serde", serde(skip))]
148 key_bindings: Option<KeyBindingsHelper>,
149
150 history: Option<crate::history::History>,
152}
153
154impl<SE: extensions::ShellExtensions> Clone for Shell<SE> {
155 fn clone(&self) -> Self {
156 Self {
157 error_formatter: self.error_formatter.clone(),
158 traps: self.traps.clone(),
159 open_files: self.open_files.clone(),
160 working_dir: self.working_dir.clone(),
161 env: self.env.clone(),
162 funcs: self.funcs.clone(),
163 options: self.options.clone(),
164 jobs: jobs::JobManager::new(),
165 aliases: self.aliases.clone(),
166 last_exit_status: self.last_exit_status,
167 last_exit_status_change_count: self.last_exit_status_change_count,
168 last_pipeline_statuses: self.last_pipeline_statuses.clone(),
169 name: self.name.clone(),
170 args: self.args.clone(),
171 version: self.version.clone(),
172 product_display_str: self.product_display_str.clone(),
173 call_stack: {
174 let mut cs = self.call_stack.clone();
179 cs.clear_active_trap_signals();
180 cs
181 },
182 directory_stack: self.directory_stack.clone(),
183 completion: self.completion.clone(),
187 builtins: self.builtins.clone(),
188 program_location_cache: self.program_location_cache.clone(),
189 last_stopwatch_time: self.last_stopwatch_time,
190 last_stopwatch_offset: self.last_stopwatch_offset,
191 parser_impl: self.parser_impl,
192 key_bindings: self.key_bindings.clone(),
193 history: self.history.clone(),
194 depth: self.depth + 1,
195 }
196 }
197}
198
199impl<SE: extensions::ShellExtensions> AsRef<Self> for Shell<SE> {
200 fn as_ref(&self) -> &Self {
201 self
202 }
203}
204
205impl<SE: extensions::ShellExtensions> AsMut<Self> for Shell<SE> {
206 fn as_mut(&mut self) -> &mut Self {
207 self
208 }
209}
210
211impl<SE: extensions::ShellExtensions> Shell<SE> {
212 pub(crate) fn new(options: CreateOptions<SE>) -> Result<Self, error::Error> {
219 let runtime_options = RuntimeOptions::defaults_from(&options);
221
222 let mut shell = Self {
224 error_formatter: options.error_formatter,
225 open_files: openfiles::OpenFiles::new(),
226 options: runtime_options,
227 name: options.shell_name,
228 args: options.shell_args.unwrap_or_default(),
229 version: options.shell_version,
230 product_display_str: options.shell_product_display_str,
231 working_dir: options.working_dir.map_or_else(std::env::current_dir, Ok)?,
232 builtins: options.builtins,
233 parser_impl: options.parser,
234 key_bindings: options.key_bindings,
235 ..Self::default()
236 };
237
238 shell.open_files.update_from(options.fds.into_iter());
240
241 shell.options.extended_globbing = true;
244
245 if !options.do_not_inherit_env {
247 wellknownvars::inherit_env_vars(&mut shell)?;
248 }
249
250 if !options.skip_well_known_vars {
252 wellknownvars::init_well_known_vars(&mut shell)?;
253 }
254
255 for (var_name, var_value) in options.vars {
257 shell.env.set_global(var_name, var_value)?;
258 }
259
260 if shell.options.enable_command_history {
263 shell.history = Some(crate::history::History::default());
264 }
265
266 Ok(shell)
267 }
268}
269
270impl<SE: extensions::ShellExtensions> Shell<SE> {
271 pub fn increment_interactive_line_offset(&mut self, delta: usize) {
278 self.call_stack.increment_current_line_offset(delta);
279 }
280
281 pub fn set_current_cmd(&mut self, cmd: &impl shrub_parser::ast::Node) {
283 self.call_stack
284 .set_current_pos(cmd.location().map(|span| span.start));
285 }
286
287 pub(crate) fn update_last_arg_variable(&mut self, last_arg: Option<String>) {
298 if self
303 .env
304 .get_using_policy("_", crate::env::EnvironmentLookup::Anywhere)
305 .is_some_and(|v| v.is_readonly())
306 {
307 return;
308 }
309
310 let value = last_arg.unwrap_or_default();
315 let _ = self
316 .env
317 .set_global("_", crate::variables::ShellVariable::new(value));
318 }
319
320 pub fn save_command_status(&self) -> SavedCommandStatus {
323 SavedCommandStatus {
324 exit_status: self.last_exit_status,
325 exit_status_change_count: self.last_exit_status_change_count,
326 pipeline_statuses: self.last_pipeline_statuses.clone(),
327 last_arg: self.env_str("_").map(|value| value.into_owned()),
328 }
329 }
330
331 pub fn restore_command_status(&mut self, saved: SavedCommandStatus) {
339 self.last_pipeline_statuses = saved.pipeline_statuses;
340 self.last_exit_status = saved.exit_status;
343 self.last_exit_status_change_count = saved.exit_status_change_count;
344 match saved.last_arg {
345 Some(last_arg) => self.update_last_arg_variable(Some(last_arg)),
346 None => _ = self.env.unset("_"),
349 }
350 }
351
352 pub const fn apply_errexit_if_enabled(&self, result: &mut ExecutionResult) {
360 if self.options.exit_on_nonzero_command_exit
361 && !result.is_success()
362 && result.is_normal_flow()
363 {
364 result.next_control_flow = ExecutionControlFlow::ExitShell;
365 }
366 }
367
368 pub(crate) fn get_keywords(&self) -> impl IntoIterator<Item = &'static str> {
371 keywords::keywords(self.options.sh_mode)
372 }
373
374 pub fn is_keyword(&self, s: &str) -> bool {
380 keywords::keywords(self.options.sh_mode).any(|keyword| keyword == s)
381 }
382
383 pub(crate) const fn last_exit_status_change_count(&self) -> usize {
384 self.last_exit_status_change_count
385 }
386}
387
388#[derive(Clone, Debug)]
396pub struct SavedCommandStatus {
397 exit_status: u8,
398 exit_status_change_count: usize,
399 pipeline_statuses: Vec<u8>,
400 last_arg: Option<String>,
401}
402
403#[inherent::inherent]
404impl<SE: extensions::ShellExtensions> ShellState for Shell<SE> {
405 pub fn is_subshell(&self) -> bool {
407 self.depth > 0
408 }
409
410 pub fn last_stopwatch_time(&self) -> std::time::SystemTime {
412 self.last_stopwatch_time
413 }
414
415 pub fn last_stopwatch_offset(&self) -> u32 {
417 self.last_stopwatch_offset
418 }
419
420 pub fn env(&self) -> &ShellEnvironment {
422 &self.env
423 }
424
425 pub fn env_mut(&mut self) -> &mut ShellEnvironment {
427 &mut self.env
428 }
429
430 pub fn options(&self) -> &RuntimeOptions {
432 &self.options
433 }
434
435 pub fn options_mut(&mut self) -> &mut RuntimeOptions {
437 &mut self.options
438 }
439
440 pub fn aliases(&self) -> &HashMap<String, String> {
442 &self.aliases
443 }
444
445 pub fn aliases_mut(&mut self) -> &mut HashMap<String, String> {
447 &mut self.aliases
448 }
449
450 pub fn jobs(&self) -> &jobs::JobManager {
452 &self.jobs
453 }
454
455 pub fn jobs_mut(&mut self) -> &mut jobs::JobManager {
457 &mut self.jobs
458 }
459
460 pub fn traps(&self) -> &crate::traps::TrapHandlerConfig {
462 &self.traps
463 }
464
465 pub fn traps_mut(&mut self) -> &mut crate::traps::TrapHandlerConfig {
467 &mut self.traps
468 }
469
470 pub fn directory_stack(&self) -> &[PathBuf] {
472 &self.directory_stack
473 }
474
475 pub fn directory_stack_mut(&mut self) -> &mut Vec<PathBuf> {
477 &mut self.directory_stack
478 }
479
480 pub fn last_pipeline_statuses(&self) -> &[u8] {
482 &self.last_pipeline_statuses
483 }
484
485 pub fn last_pipeline_statuses_mut(&mut self) -> &mut Vec<u8> {
488 &mut self.last_pipeline_statuses
489 }
490
491 pub fn program_location_cache(&self) -> &pathcache::PathCache {
493 &self.program_location_cache
494 }
495
496 pub fn program_location_cache_mut(&mut self) -> &mut pathcache::PathCache {
498 &mut self.program_location_cache
499 }
500
501 pub fn completion_config(&self) -> &crate::completion::Config {
503 &self.completion.config
504 }
505
506 pub fn completion_config_mut(&mut self) -> &mut crate::completion::Config {
508 &mut self.completion.config
509 }
510
511 pub fn open_files(&self) -> &openfiles::OpenFiles {
513 &self.open_files
514 }
515
516 pub fn open_files_mut(&mut self) -> &mut openfiles::OpenFiles {
518 &mut self.open_files
519 }
520
521 pub fn current_shell_name(&self) -> Option<Cow<'_, str>> {
524 for frame in self.call_stack.iter() {
525 if frame.frame_type.is_run_script() {
527 return Some(frame.frame_type.name());
528 }
529 }
530
531 self.name.as_deref().map(|name| name.into())
532 }
533
534 pub fn depth(&self) -> usize {
537 self.depth
538 }
539
540 pub fn call_stack(&self) -> &crate::callstack::CallStack {
542 &self.call_stack
543 }
544
545 pub fn history(&self) -> Option<&crate::history::History> {
547 self.history.as_ref()
548 }
549
550 pub fn history_mut(&mut self) -> Option<&mut crate::history::History> {
552 self.history.as_mut()
553 }
554
555 pub fn version(&self) -> Option<&str> {
557 self.version.as_deref()
558 }
559
560 pub fn last_exit_status(&self) -> u8 {
562 self.last_exit_status
563 }
564
565 pub fn set_last_exit_status(&mut self, status: u8) {
569 self.last_exit_status = status;
570 self.last_exit_status_change_count += 1;
571 }
572
573 pub fn key_bindings(&self) -> Option<&KeyBindingsHelper> {
575 self.key_bindings.as_ref()
576 }
577
578 pub fn set_key_bindings(&mut self, key_bindings: Option<KeyBindingsHelper>) {
580 self.key_bindings = key_bindings;
581 }
582
583 pub fn working_dir(&self) -> &Path {
585 &self.working_dir
586 }
587
588 pub(crate) fn working_dir_mut(&mut self) -> &mut PathBuf {
591 &mut self.working_dir
592 }
593
594 pub fn product_display_str(&self) -> Option<&str> {
596 self.product_display_str.as_deref()
597 }
598}
599
600#[cfg(feature = "serde")]
601fn default_error_formatter<EF: extensions::ErrorFormatter>() -> EF {
602 EF::default()
603}
604
605#[cfg(test)]
606#[allow(clippy::panic_in_result_fn, reason = "assertions in a fallible test")]
607mod tests {
608 use super::*;
609
610 #[tokio::test]
615 async fn saved_command_status_round_trips_last_arg() -> Result<(), error::Error> {
616 let mut shell = Shell::builder()
617 .profile(ProfileLoadBehavior::Skip)
618 .rc(RcLoadBehavior::Skip)
619 .build()
620 .await?;
621
622 shell.update_last_arg_variable(Some(String::from("saved")));
623 let with_value = shell.save_command_status();
624
625 shell.env.unset("_")?;
626 let while_unset = shell.save_command_status();
627
628 shell.restore_command_status(with_value);
629 assert_eq!(shell.env_str("_").as_deref(), Some("saved"));
630
631 shell.restore_command_status(while_unset);
632 assert_eq!(shell.env_str("_"), None);
633
634 Ok(())
635 }
636
637 #[tokio::test]
638 async fn unset_env_str_contract_is_independent_of_ifs_default() -> Result<(), error::Error> {
639 let mut shell = Shell::builder()
640 .profile(ProfileLoadBehavior::Skip)
641 .rc(RcLoadBehavior::Skip)
642 .build()
643 .await?;
644
645 shell.env.set_global(
646 "IFS",
647 crate::ShellVariable::new(crate::ShellValue::Unset(
648 crate::variables::ShellValueUnsetType::Untyped,
649 )),
650 )?;
651 assert_eq!(shell.env_str("IFS").as_deref(), Some(""));
652 assert_eq!(shell.ifs(), " \t\n");
653
654 shell.env.set_global("IFS", crate::ShellVariable::new(""))?;
655 assert_eq!(shell.env_str("IFS").as_deref(), Some(""));
656 assert_eq!(shell.ifs(), "");
657
658 Ok(())
659 }
660}