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 snapshot;
47mod state;
48mod traps;
49
50pub use builder::{CreateOptions, ShellBuilder, ShellBuilderState};
51pub use initscripts::{ProfileLoadBehavior, RcLoadBehavior};
52pub use state::ShellState;
53
54#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
63pub struct Shell<SE: extensions::ShellExtensions = extensions::DefaultShellExtensions> {
64 #[cfg_attr(feature = "serde", serde(skip, default = "default_error_formatter"))]
66 error_formatter: SE::ErrorFormatter,
67
68 traps: crate::traps::TrapHandlerConfig,
70
71 open_files: openfiles::OpenFiles,
73
74 working_dir: PathBuf,
76
77 env: ShellEnvironment,
79
80 funcs: functions::FunctionEnv,
82
83 options: RuntimeOptions,
85
86 #[cfg_attr(feature = "serde", serde(skip))]
89 jobs: jobs::JobManager,
90
91 aliases: HashMap<String, String>,
93
94 last_exit_status: u8,
96
97 last_exit_status_change_count: usize,
101
102 last_pipeline_statuses: Vec<u8>,
104
105 depth: usize,
107
108 name: Option<String>,
110
111 args: Vec<String>,
113
114 version: Option<String>,
116
117 product_display_str: Option<String>,
119
120 call_stack: crate::callstack::CallStack,
122
123 directory_stack: Vec<PathBuf>,
125
126 completion: crate::completion::State,
128
129 #[cfg_attr(feature = "serde", serde(skip))]
131 builtins: HashMap<String, builtins::Registration<SE>>,
132
133 program_location_cache: pathcache::PathCache,
135
136 last_stopwatch_time: std::time::SystemTime,
138
139 last_stopwatch_offset: u32,
141
142 #[cfg_attr(feature = "serde", serde(skip))]
144 parser_impl: crate::parser::ParserImpl,
145
146 #[cfg_attr(feature = "serde", serde(skip))]
149 key_bindings: Option<KeyBindingsHelper>,
150
151 history: Option<crate::history::History>,
153}
154
155impl<SE: extensions::ShellExtensions> Clone for Shell<SE> {
156 fn clone(&self) -> Self {
157 Self {
158 error_formatter: self.error_formatter.clone(),
159 traps: self.traps.clone(),
160 open_files: self.open_files.clone(),
161 working_dir: self.working_dir.clone(),
162 env: self.env.clone(),
163 funcs: self.funcs.clone(),
164 options: self.options.clone(),
165 jobs: jobs::JobManager::new(),
166 aliases: self.aliases.clone(),
167 last_exit_status: self.last_exit_status,
168 last_exit_status_change_count: self.last_exit_status_change_count,
169 last_pipeline_statuses: self.last_pipeline_statuses.clone(),
170 name: self.name.clone(),
171 args: self.args.clone(),
172 version: self.version.clone(),
173 product_display_str: self.product_display_str.clone(),
174 call_stack: {
175 let mut cs = self.call_stack.clone();
180 cs.clear_active_trap_signals();
181 cs
182 },
183 directory_stack: self.directory_stack.clone(),
184 completion: self.completion.clone(),
188 builtins: self.builtins.clone(),
189 program_location_cache: self.program_location_cache.clone(),
190 last_stopwatch_time: self.last_stopwatch_time,
191 last_stopwatch_offset: self.last_stopwatch_offset,
192 parser_impl: self.parser_impl,
193 key_bindings: self.key_bindings.clone(),
194 history: self.history.clone(),
195 depth: self.depth + 1,
196 }
197 }
198}
199
200impl<SE: extensions::ShellExtensions> AsRef<Self> for Shell<SE> {
201 fn as_ref(&self) -> &Self {
202 self
203 }
204}
205
206impl<SE: extensions::ShellExtensions> AsMut<Self> for Shell<SE> {
207 fn as_mut(&mut self) -> &mut Self {
208 self
209 }
210}
211
212impl<SE: extensions::ShellExtensions> Shell<SE> {
213 pub(crate) fn new(options: CreateOptions<SE>) -> Result<Self, error::Error> {
220 let runtime_options = RuntimeOptions::defaults_from(&options);
222
223 let mut shell = Self {
225 error_formatter: options.error_formatter,
226 open_files: openfiles::OpenFiles::new(),
227 options: runtime_options,
228 name: options.shell_name,
229 args: options.shell_args.unwrap_or_default(),
230 version: options.shell_version,
231 product_display_str: options.shell_product_display_str,
232 working_dir: options.working_dir.map_or_else(std::env::current_dir, Ok)?,
233 builtins: options.builtins,
234 parser_impl: options.parser,
235 key_bindings: options.key_bindings,
236 ..Self::default()
237 };
238
239 shell.open_files.update_from(options.fds.into_iter());
241
242 shell.options.extended_globbing = true;
245
246 if !options.do_not_inherit_env {
248 wellknownvars::inherit_env_vars(&mut shell)?;
249 }
250
251 if !options.skip_well_known_vars {
253 wellknownvars::init_well_known_vars(&mut shell)?;
254 }
255
256 for (var_name, var_value) in options.vars {
258 shell.env.set_global(var_name, var_value)?;
259 }
260
261 if shell.options.enable_command_history {
264 shell.history = Some(crate::history::History::default());
265 }
266
267 Ok(shell)
268 }
269}
270
271impl<SE: extensions::ShellExtensions> Shell<SE> {
272 pub fn increment_interactive_line_offset(&mut self, delta: usize) {
279 self.call_stack.increment_current_line_offset(delta);
280 }
281
282 pub fn set_current_cmd(&mut self, cmd: &impl shrub_parser::ast::Node) {
284 self.call_stack
285 .set_current_pos(cmd.location().map(|span| span.start));
286 }
287
288 pub(crate) fn update_last_arg_variable(&mut self, last_arg: Option<String>) {
299 if self
304 .env
305 .get_using_policy("_", crate::env::EnvironmentLookup::Anywhere)
306 .is_some_and(|v| v.is_readonly())
307 {
308 return;
309 }
310
311 let value = last_arg.unwrap_or_default();
316 let _ = self
317 .env
318 .set_global("_", crate::variables::ShellVariable::new(value));
319 }
320
321 pub fn save_command_status(&self) -> SavedCommandStatus {
324 SavedCommandStatus {
325 exit_status: self.last_exit_status,
326 exit_status_change_count: self.last_exit_status_change_count,
327 pipeline_statuses: self.last_pipeline_statuses.clone(),
328 last_arg: self.env_str("_").map(|value| value.into_owned()),
329 }
330 }
331
332 pub fn restore_command_status(&mut self, saved: SavedCommandStatus) {
340 self.last_pipeline_statuses = saved.pipeline_statuses;
341 self.last_exit_status = saved.exit_status;
344 self.last_exit_status_change_count = saved.exit_status_change_count;
345 match saved.last_arg {
346 Some(last_arg) => self.update_last_arg_variable(Some(last_arg)),
347 None => _ = self.env.unset("_"),
350 }
351 }
352
353 pub const fn apply_errexit_if_enabled(&self, result: &mut ExecutionResult) {
361 if self.options.exit_on_nonzero_command_exit
362 && !result.is_success()
363 && result.is_normal_flow()
364 {
365 result.next_control_flow = ExecutionControlFlow::ExitShell;
366 }
367 }
368
369 pub(crate) fn get_keywords(&self) -> impl IntoIterator<Item = &'static str> {
372 keywords::keywords(self.options.sh_mode)
373 }
374
375 pub fn is_keyword(&self, s: &str) -> bool {
381 keywords::keywords(self.options.sh_mode).any(|keyword| keyword == s)
382 }
383
384 pub(crate) const fn last_exit_status_change_count(&self) -> usize {
385 self.last_exit_status_change_count
386 }
387}
388
389#[derive(Clone, Debug)]
397pub struct SavedCommandStatus {
398 exit_status: u8,
399 exit_status_change_count: usize,
400 pipeline_statuses: Vec<u8>,
401 last_arg: Option<String>,
402}
403
404#[inherent::inherent]
405impl<SE: extensions::ShellExtensions> ShellState for Shell<SE> {
406 pub fn is_subshell(&self) -> bool {
408 self.depth > 0
409 }
410
411 pub fn last_stopwatch_time(&self) -> std::time::SystemTime {
413 self.last_stopwatch_time
414 }
415
416 pub fn last_stopwatch_offset(&self) -> u32 {
418 self.last_stopwatch_offset
419 }
420
421 pub fn env(&self) -> &ShellEnvironment {
423 &self.env
424 }
425
426 pub fn env_mut(&mut self) -> &mut ShellEnvironment {
428 &mut self.env
429 }
430
431 pub fn options(&self) -> &RuntimeOptions {
433 &self.options
434 }
435
436 pub fn options_mut(&mut self) -> &mut RuntimeOptions {
438 &mut self.options
439 }
440
441 pub fn aliases(&self) -> &HashMap<String, String> {
443 &self.aliases
444 }
445
446 pub fn aliases_mut(&mut self) -> &mut HashMap<String, String> {
448 &mut self.aliases
449 }
450
451 pub fn jobs(&self) -> &jobs::JobManager {
453 &self.jobs
454 }
455
456 pub fn jobs_mut(&mut self) -> &mut jobs::JobManager {
458 &mut self.jobs
459 }
460
461 pub fn traps(&self) -> &crate::traps::TrapHandlerConfig {
463 &self.traps
464 }
465
466 pub fn traps_mut(&mut self) -> &mut crate::traps::TrapHandlerConfig {
468 &mut self.traps
469 }
470
471 pub fn directory_stack(&self) -> &[PathBuf] {
473 &self.directory_stack
474 }
475
476 pub fn directory_stack_mut(&mut self) -> &mut Vec<PathBuf> {
478 &mut self.directory_stack
479 }
480
481 pub fn last_pipeline_statuses(&self) -> &[u8] {
483 &self.last_pipeline_statuses
484 }
485
486 pub fn last_pipeline_statuses_mut(&mut self) -> &mut Vec<u8> {
489 &mut self.last_pipeline_statuses
490 }
491
492 pub fn program_location_cache(&self) -> &pathcache::PathCache {
494 &self.program_location_cache
495 }
496
497 pub fn program_location_cache_mut(&mut self) -> &mut pathcache::PathCache {
499 &mut self.program_location_cache
500 }
501
502 pub fn completion_config(&self) -> &crate::completion::Config {
504 &self.completion.config
505 }
506
507 pub fn completion_config_mut(&mut self) -> &mut crate::completion::Config {
509 &mut self.completion.config
510 }
511
512 pub fn open_files(&self) -> &openfiles::OpenFiles {
514 &self.open_files
515 }
516
517 pub fn open_files_mut(&mut self) -> &mut openfiles::OpenFiles {
519 &mut self.open_files
520 }
521
522 pub fn current_shell_name(&self) -> Option<Cow<'_, str>> {
525 for frame in self.call_stack.iter() {
526 if frame.frame_type.is_run_script() {
528 return Some(frame.frame_type.name());
529 }
530 }
531
532 self.name.as_deref().map(|name| name.into())
533 }
534
535 pub fn depth(&self) -> usize {
538 self.depth
539 }
540
541 pub fn call_stack(&self) -> &crate::callstack::CallStack {
543 &self.call_stack
544 }
545
546 pub fn history(&self) -> Option<&crate::history::History> {
548 self.history.as_ref()
549 }
550
551 pub fn history_mut(&mut self) -> Option<&mut crate::history::History> {
553 self.history.as_mut()
554 }
555
556 pub fn version(&self) -> Option<&str> {
558 self.version.as_deref()
559 }
560
561 pub fn last_exit_status(&self) -> u8 {
563 self.last_exit_status
564 }
565
566 pub fn set_last_exit_status(&mut self, status: u8) {
570 self.last_exit_status = status;
571 self.last_exit_status_change_count += 1;
572 }
573
574 pub fn key_bindings(&self) -> Option<&KeyBindingsHelper> {
576 self.key_bindings.as_ref()
577 }
578
579 pub fn set_key_bindings(&mut self, key_bindings: Option<KeyBindingsHelper>) {
581 self.key_bindings = key_bindings;
582 }
583
584 pub fn working_dir(&self) -> &Path {
586 &self.working_dir
587 }
588
589 pub(crate) fn working_dir_mut(&mut self) -> &mut PathBuf {
592 &mut self.working_dir
593 }
594
595 pub fn product_display_str(&self) -> Option<&str> {
597 self.product_display_str.as_deref()
598 }
599}
600
601#[cfg(feature = "serde")]
602fn default_error_formatter<EF: extensions::ErrorFormatter>() -> EF {
603 EF::default()
604}
605
606#[cfg(test)]
607#[allow(clippy::panic_in_result_fn, reason = "assertions in a fallible test")]
608mod tests {
609 use super::*;
610
611 #[tokio::test]
616 async fn saved_command_status_round_trips_last_arg() -> Result<(), error::Error> {
617 let mut shell = Shell::builder()
618 .profile(ProfileLoadBehavior::Skip)
619 .rc(RcLoadBehavior::Skip)
620 .build()
621 .await?;
622
623 shell.update_last_arg_variable(Some(String::from("saved")));
624 let with_value = shell.save_command_status();
625
626 shell.env.unset("_")?;
627 let while_unset = shell.save_command_status();
628
629 shell.restore_command_status(with_value);
630 assert_eq!(shell.env_str("_").as_deref(), Some("saved"));
631
632 shell.restore_command_status(while_unset);
633 assert_eq!(shell.env_str("_"), None);
634
635 Ok(())
636 }
637
638 #[tokio::test]
639 async fn unset_env_str_contract_is_independent_of_ifs_default() -> Result<(), error::Error> {
640 let mut shell = Shell::builder()
641 .profile(ProfileLoadBehavior::Skip)
642 .rc(RcLoadBehavior::Skip)
643 .build()
644 .await?;
645
646 shell.env.set_global(
647 "IFS",
648 crate::ShellVariable::new(crate::ShellValue::Unset(
649 crate::variables::ShellValueUnsetType::Untyped,
650 )),
651 )?;
652 assert_eq!(shell.env_str("IFS").as_deref(), Some(""));
653 assert_eq!(shell.ifs(), " \t\n");
654
655 shell.env.set_global("IFS", crate::ShellVariable::new(""))?;
656 assert_eq!(shell.env_str("IFS").as_deref(), Some(""));
657 assert_eq!(shell.ifs(), "");
658
659 Ok(())
660 }
661}