Skip to main content

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        "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}