nu_command/system/
exec.rs

1use std::borrow::Cow;
2
3use nu_engine::{command_prelude::*, env_to_strings};
4
5#[derive(Clone)]
6pub struct Exec;
7
8impl Command for Exec {
9    fn name(&self) -> &str {
10        "exec"
11    }
12
13    fn signature(&self) -> Signature {
14        Signature::build("exec")
15            .input_output_types(vec![(Type::Nothing, Type::Any)])
16            .rest(
17                "command",
18                SyntaxShape::OneOf(vec![SyntaxShape::GlobPattern, SyntaxShape::Any]),
19                "External command to run, with arguments.",
20            )
21            .allows_unknown_args()
22            .category(Category::System)
23    }
24
25    fn description(&self) -> &str {
26        "Execute a command, replacing or exiting the current process, depending on platform."
27    }
28
29    fn extra_description(&self) -> &str {
30        r#"On Unix-based systems, the current process is replaced with the command.
31On Windows based systems, Nushell will wait for the command to finish and then exit with the command's exit code."#
32    }
33
34    fn run(
35        &self,
36        engine_state: &EngineState,
37        stack: &mut Stack,
38        call: &Call,
39        _input: PipelineData,
40    ) -> Result<PipelineData, ShellError> {
41        let cwd = engine_state.cwd(Some(stack))?;
42        let rest = call.rest::<Value>(engine_state, stack, 0)?;
43        let name_args = rest.split_first();
44
45        let Some((name, call_args)) = name_args else {
46            return Err(ShellError::MissingParameter {
47                param_name: "no command given".into(),
48                span: call.head,
49            });
50        };
51
52        let name_str: Cow<str> = match &name {
53            Value::Glob { val, .. } => Cow::Borrowed(val),
54            Value::String { val, .. } => Cow::Borrowed(val),
55            _ => Cow::Owned(name.clone().coerce_into_string()?),
56        };
57
58        // Find the absolute path to the executable. If the command is not
59        // found, display a helpful error message.
60        let executable = {
61            let paths = nu_engine::env::path_str(engine_state, stack, call.head)?;
62            let Some(executable) = crate::which(name_str.as_ref(), &paths, cwd.as_ref()) else {
63                return Err(crate::command_not_found(
64                    &name_str,
65                    call.head,
66                    engine_state,
67                    stack,
68                    &cwd,
69                ));
70            };
71            executable
72        };
73
74        // Create the command.
75        let mut command = std::process::Command::new(executable);
76
77        // Configure PWD.
78        command.current_dir(cwd);
79
80        // Configure environment variables.
81        let envs = env_to_strings(engine_state, stack)?;
82        command.env_clear();
83        command.envs(envs);
84        // Decrement SHLVL as removing the current shell from the stack
85        // (only works in interactive mode, same as initialization)
86        if engine_state.is_interactive {
87            let shlvl = engine_state
88                .get_env_var("SHLVL")
89                .and_then(|shlvl_env| shlvl_env.coerce_str().ok()?.parse::<i64>().ok())
90                .unwrap_or(1)
91                .saturating_sub(1);
92            command.env("SHLVL", shlvl.to_string());
93        }
94
95        // Configure args.
96        let args = crate::eval_external_arguments(engine_state, stack, call_args.to_vec())?;
97        command.args(args.into_iter().map(|s| s.item));
98
99        // Execute the child process, replacing/terminating the current process
100        // depending on platform.
101        #[cfg(unix)]
102        {
103            use std::os::unix::process::CommandExt;
104
105            let err = command.exec();
106            Err(ShellError::ExternalCommand {
107                label: "Failed to exec into new process".into(),
108                help: err.to_string(),
109                span: call.head,
110            })
111        }
112        #[cfg(windows)]
113        {
114            let mut child = command.spawn().map_err(|err| ShellError::ExternalCommand {
115                label: "Failed to exec into new process".into(),
116                help: err.to_string(),
117                span: call.head,
118            })?;
119            let status = child.wait().map_err(|err| ShellError::ExternalCommand {
120                label: "Failed to wait for child process".into(),
121                help: err.to_string(),
122                span: call.head,
123            })?;
124            std::process::exit(status.code().expect("status.code() succeeds on Windows"))
125        }
126    }
127
128    fn examples(&self) -> Vec<Example> {
129        vec![
130            Example {
131                description: "Execute external 'ps aux' tool",
132                example: "exec ps aux",
133                result: None,
134            },
135            Example {
136                description: "Execute 'nautilus'",
137                example: "exec nautilus",
138                result: None,
139            },
140        ]
141    }
142}