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.copy_at_depth(self.depth + 1)
158 }
159}
160
161impl<SE: extensions::ShellExtensions> Shell<SE> {
162 #[must_use]
176 pub fn fork_state(&self) -> Self {
177 self.copy_at_depth(self.depth)
178 }
179
180 fn copy_at_depth(&self, depth: usize) -> Self {
181 Self {
182 error_formatter: self.error_formatter.clone(),
183 traps: self.traps.clone(),
184 open_files: self.open_files.clone(),
185 working_dir: self.working_dir.clone(),
186 env: self.env.clone(),
187 funcs: self.funcs.clone(),
188 options: self.options.clone(),
189 jobs: jobs::JobManager::new(),
190 aliases: self.aliases.clone(),
191 last_exit_status: self.last_exit_status,
192 last_exit_status_change_count: self.last_exit_status_change_count,
193 last_pipeline_statuses: self.last_pipeline_statuses.clone(),
194 name: self.name.clone(),
195 args: self.args.clone(),
196 version: self.version.clone(),
197 product_display_str: self.product_display_str.clone(),
198 call_stack: {
199 let mut cs = self.call_stack.clone();
204 cs.clear_active_trap_signals();
205 cs
206 },
207 directory_stack: self.directory_stack.clone(),
208 completion: self.completion.clone(),
212 builtins: self.builtins.clone(),
213 program_location_cache: self.program_location_cache.clone(),
214 last_stopwatch_time: self.last_stopwatch_time,
215 last_stopwatch_offset: self.last_stopwatch_offset,
216 parser_impl: self.parser_impl,
217 key_bindings: self.key_bindings.clone(),
218 history: self.history.clone(),
219 depth,
220 }
221 }
222}
223
224impl<SE: extensions::ShellExtensions> AsRef<Self> for Shell<SE> {
225 fn as_ref(&self) -> &Self {
226 self
227 }
228}
229
230impl<SE: extensions::ShellExtensions> AsMut<Self> for Shell<SE> {
231 fn as_mut(&mut self) -> &mut Self {
232 self
233 }
234}
235
236impl<SE: extensions::ShellExtensions> Shell<SE> {
237 pub(crate) fn new(options: CreateOptions<SE>) -> Result<Self, error::Error> {
244 let runtime_options = RuntimeOptions::defaults_from(&options);
246
247 let mut shell = Self {
249 error_formatter: options.error_formatter,
250 open_files: openfiles::OpenFiles::new(),
251 options: runtime_options,
252 name: options.shell_name,
253 args: options.shell_args.unwrap_or_default(),
254 version: options.shell_version,
255 product_display_str: options.shell_product_display_str,
256 working_dir: options.working_dir.map_or_else(std::env::current_dir, Ok)?,
257 builtins: options.builtins,
258 parser_impl: options.parser,
259 key_bindings: options.key_bindings,
260 ..Self::default()
261 };
262
263 shell.open_files.update_from(options.fds.into_iter());
265
266 shell.options.extended_globbing = true;
269
270 if !options.do_not_inherit_env {
272 wellknownvars::inherit_env_vars(&mut shell)?;
273 }
274
275 if !options.skip_well_known_vars {
277 wellknownvars::init_well_known_vars(&mut shell)?;
278 }
279
280 for (var_name, var_value) in options.vars {
282 shell.env.set_global(var_name, var_value)?;
283 }
284
285 if shell.options.enable_command_history {
288 shell.history = Some(crate::history::History::default());
289 }
290
291 Ok(shell)
292 }
293}
294
295impl<SE: extensions::ShellExtensions> Shell<SE> {
296 pub fn increment_interactive_line_offset(&mut self, delta: usize) {
303 self.call_stack.increment_current_line_offset(delta);
304 }
305
306 pub fn set_current_cmd(&mut self, cmd: &impl shrub_parser::ast::Node) {
308 self.call_stack
309 .set_current_pos(cmd.location().map(|span| span.start));
310 }
311
312 pub(crate) fn update_last_arg_variable(&mut self, last_arg: Option<String>) {
323 if self
328 .env
329 .get_using_policy("_", crate::env::EnvironmentLookup::Anywhere)
330 .is_some_and(|v| v.is_readonly())
331 {
332 return;
333 }
334
335 let value = last_arg.unwrap_or_default();
340 let _ = self
341 .env
342 .set_global("_", crate::variables::ShellVariable::new(value));
343 }
344
345 pub fn save_command_status(&self) -> SavedCommandStatus {
348 SavedCommandStatus {
349 exit_status: self.last_exit_status,
350 exit_status_change_count: self.last_exit_status_change_count,
351 pipeline_statuses: self.last_pipeline_statuses.clone(),
352 last_arg: self.env_str("_").map(|value| value.into_owned()),
353 }
354 }
355
356 pub fn restore_command_status(&mut self, saved: SavedCommandStatus) {
364 self.last_pipeline_statuses = saved.pipeline_statuses;
365 self.last_exit_status = saved.exit_status;
368 self.last_exit_status_change_count = saved.exit_status_change_count;
369 match saved.last_arg {
370 Some(last_arg) => self.update_last_arg_variable(Some(last_arg)),
371 None => _ = self.env.unset("_"),
374 }
375 }
376
377 pub const fn apply_errexit_if_enabled(&self, result: &mut ExecutionResult) {
385 if self.options.exit_on_nonzero_command_exit
386 && !result.is_success()
387 && result.is_normal_flow()
388 {
389 result.next_control_flow = ExecutionControlFlow::ExitShell;
390 }
391 }
392
393 pub(crate) fn get_keywords(&self) -> impl IntoIterator<Item = &'static str> {
396 keywords::keywords(self.options.sh_mode)
397 }
398
399 pub fn is_keyword(&self, s: &str) -> bool {
405 keywords::keywords(self.options.sh_mode).any(|keyword| keyword == s)
406 }
407
408 pub(crate) const fn last_exit_status_change_count(&self) -> usize {
409 self.last_exit_status_change_count
410 }
411}
412
413#[derive(Clone, Debug)]
421pub struct SavedCommandStatus {
422 exit_status: u8,
423 exit_status_change_count: usize,
424 pipeline_statuses: Vec<u8>,
425 last_arg: Option<String>,
426}
427
428#[inherent::inherent]
429impl<SE: extensions::ShellExtensions> ShellState for Shell<SE> {
430 pub fn is_subshell(&self) -> bool {
432 self.depth > 0
433 }
434
435 pub fn last_stopwatch_time(&self) -> std::time::SystemTime {
437 self.last_stopwatch_time
438 }
439
440 pub fn last_stopwatch_offset(&self) -> u32 {
442 self.last_stopwatch_offset
443 }
444
445 pub fn env(&self) -> &ShellEnvironment {
447 &self.env
448 }
449
450 pub fn env_mut(&mut self) -> &mut ShellEnvironment {
452 &mut self.env
453 }
454
455 pub fn options(&self) -> &RuntimeOptions {
457 &self.options
458 }
459
460 pub fn options_mut(&mut self) -> &mut RuntimeOptions {
462 &mut self.options
463 }
464
465 pub fn aliases(&self) -> &HashMap<String, String> {
467 &self.aliases
468 }
469
470 pub fn aliases_mut(&mut self) -> &mut HashMap<String, String> {
472 &mut self.aliases
473 }
474
475 pub fn jobs(&self) -> &jobs::JobManager {
477 &self.jobs
478 }
479
480 pub fn jobs_mut(&mut self) -> &mut jobs::JobManager {
482 &mut self.jobs
483 }
484
485 pub fn traps(&self) -> &crate::traps::TrapHandlerConfig {
487 &self.traps
488 }
489
490 pub fn traps_mut(&mut self) -> &mut crate::traps::TrapHandlerConfig {
492 &mut self.traps
493 }
494
495 pub fn directory_stack(&self) -> &[PathBuf] {
497 &self.directory_stack
498 }
499
500 pub fn directory_stack_mut(&mut self) -> &mut Vec<PathBuf> {
502 &mut self.directory_stack
503 }
504
505 pub fn last_pipeline_statuses(&self) -> &[u8] {
507 &self.last_pipeline_statuses
508 }
509
510 pub fn last_pipeline_statuses_mut(&mut self) -> &mut Vec<u8> {
513 &mut self.last_pipeline_statuses
514 }
515
516 pub fn program_location_cache(&self) -> &pathcache::PathCache {
518 &self.program_location_cache
519 }
520
521 pub fn program_location_cache_mut(&mut self) -> &mut pathcache::PathCache {
523 &mut self.program_location_cache
524 }
525
526 pub fn completion_config(&self) -> &crate::completion::Config {
528 &self.completion.config
529 }
530
531 pub fn completion_config_mut(&mut self) -> &mut crate::completion::Config {
533 &mut self.completion.config
534 }
535
536 pub fn open_files(&self) -> &openfiles::OpenFiles {
538 &self.open_files
539 }
540
541 pub fn open_files_mut(&mut self) -> &mut openfiles::OpenFiles {
543 &mut self.open_files
544 }
545
546 pub fn current_shell_name(&self) -> Option<Cow<'_, str>> {
549 for frame in self.call_stack.iter() {
550 if frame.frame_type.is_run_script() {
552 return Some(frame.frame_type.name());
553 }
554 }
555
556 self.name.as_deref().map(|name| name.into())
557 }
558
559 pub fn depth(&self) -> usize {
562 self.depth
563 }
564
565 pub fn call_stack(&self) -> &crate::callstack::CallStack {
567 &self.call_stack
568 }
569
570 pub fn history(&self) -> Option<&crate::history::History> {
572 self.history.as_ref()
573 }
574
575 pub fn history_mut(&mut self) -> Option<&mut crate::history::History> {
577 self.history.as_mut()
578 }
579
580 pub fn version(&self) -> Option<&str> {
582 self.version.as_deref()
583 }
584
585 pub fn last_exit_status(&self) -> u8 {
587 self.last_exit_status
588 }
589
590 pub fn set_last_exit_status(&mut self, status: u8) {
594 self.last_exit_status = status;
595 self.last_exit_status_change_count += 1;
596 }
597
598 pub fn key_bindings(&self) -> Option<&KeyBindingsHelper> {
600 self.key_bindings.as_ref()
601 }
602
603 pub fn set_key_bindings(&mut self, key_bindings: Option<KeyBindingsHelper>) {
605 self.key_bindings = key_bindings;
606 }
607
608 pub fn working_dir(&self) -> &Path {
610 &self.working_dir
611 }
612
613 pub(crate) fn working_dir_mut(&mut self) -> &mut PathBuf {
616 &mut self.working_dir
617 }
618
619 pub fn product_display_str(&self) -> Option<&str> {
621 self.product_display_str.as_deref()
622 }
623}
624
625#[cfg(feature = "serde")]
626fn default_error_formatter<EF: extensions::ErrorFormatter>() -> EF {
627 EF::default()
628}
629
630#[cfg(test)]
631#[allow(clippy::panic_in_result_fn, reason = "assertions in a fallible test")]
632mod tests {
633 use super::*;
634
635 #[tokio::test]
640 async fn saved_command_status_round_trips_last_arg() -> Result<(), error::Error> {
641 let mut shell = Shell::builder()
642 .profile(ProfileLoadBehavior::Skip)
643 .rc(RcLoadBehavior::Skip)
644 .build()
645 .await?;
646
647 shell.update_last_arg_variable(Some(String::from("saved")));
648 let with_value = shell.save_command_status();
649
650 shell.env.unset("_")?;
651 let while_unset = shell.save_command_status();
652
653 shell.restore_command_status(with_value);
654 assert_eq!(shell.env_str("_").as_deref(), Some("saved"));
655
656 shell.restore_command_status(while_unset);
657 assert_eq!(shell.env_str("_"), None);
658
659 Ok(())
660 }
661
662 #[tokio::test]
663 async fn unset_env_str_contract_is_independent_of_ifs_default() -> Result<(), error::Error> {
664 let mut shell = Shell::builder()
665 .profile(ProfileLoadBehavior::Skip)
666 .rc(RcLoadBehavior::Skip)
667 .build()
668 .await?;
669
670 shell.env.set_global(
671 "IFS",
672 crate::ShellVariable::new(crate::ShellValue::Unset(
673 crate::variables::ShellValueUnsetType::Untyped,
674 )),
675 )?;
676 assert_eq!(shell.env_str("IFS").as_deref(), Some(""));
677 assert_eq!(shell.ifs(), " \t\n");
678
679 shell.env.set_global("IFS", crate::ShellVariable::new(""))?;
680 assert_eq!(shell.env_str("IFS").as_deref(), Some(""));
681 assert_eq!(shell.ifs(), "");
682
683 Ok(())
684 }
685}