Add exec command for Windows (#11001)

# Description
Based of the work and discussion in #10844, this PR adds the `exec`
command for Windows. This is done by simply spawning a
`std::process::Command` and then immediately exiting via
`std::process::exit` once the child process is finished. The child
process's exit code is passed to `exit`.

# User-Facing Changes
The `exec` command is now available on Windows, and there should be no
change in behaviour for Unix systems.
This commit is contained in:
Ian Manske 2023-11-08 20:50:25 +00:00 committed by GitHub
parent 59ea28cf06
commit 1fd3bc1ba6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 41 additions and 33 deletions

View File

@ -115,6 +115,7 @@ pub fn add_shell_command_context(mut engine_state: EngineState) -> EngineState {
bind_command! { bind_command! {
Complete, Complete,
External, External,
Exec,
NuCheck, NuCheck,
Sys, Sys,
}; };
@ -145,9 +146,6 @@ pub fn add_shell_command_context(mut engine_state: EngineState) -> EngineState {
ViewSpan, ViewSpan,
}; };
#[cfg(unix)]
bind_command! { Exec }
#[cfg(windows)] #[cfg(windows)]
bind_command! { RegistryQuery } bind_command! { RegistryQuery }

View File

@ -1,11 +1,10 @@
use super::run_external::create_external_command; use super::run_external::create_external_command;
use nu_engine::{current_dir, CallExt}; use nu_engine::current_dir;
use nu_protocol::{ use nu_protocol::{
ast::Call, ast::Call,
engine::{Command, EngineState, Stack}, engine::{Command, EngineState, Stack},
Category, Example, PipelineData, ShellError, Signature, Spanned, SyntaxShape, Type, Category, Example, PipelineData, ShellError, Signature, Span, SyntaxShape, Type,
}; };
use std::os::unix::process::CommandExt;
#[derive(Clone)] #[derive(Clone)]
pub struct Exec; pub struct Exec;
@ -24,11 +23,12 @@ impl Command for Exec {
} }
fn usage(&self) -> &str { fn usage(&self) -> &str {
"Execute a command, replacing the current process." "Execute a command, replacing or exiting the current process, depending on platform."
} }
fn extra_usage(&self) -> &str { fn extra_usage(&self) -> &str {
"Currently supported only on Unix-based systems." r#"On Unix-based systems, the current process is replaced with the command.
On Windows based systems, Nushell will wait for the command to finish and then exit with the command's exit code."#
} }
fn run( fn run(
@ -62,36 +62,49 @@ fn exec(
stack: &mut Stack, stack: &mut Stack,
call: &Call, call: &Call,
) -> Result<PipelineData, ShellError> { ) -> Result<PipelineData, ShellError> {
let name: Spanned<String> = call.req(engine_state, stack, 0)?; let external_command =
let name_span = name.span; create_external_command(engine_state, stack, call, false, false, false, false)?;
let redirect_stdout = call.has_flag("redirect-stdout");
let redirect_stderr = call.has_flag("redirect-stderr");
let redirect_combine = call.has_flag("redirect-combine");
let trim_end_newline = call.has_flag("trim-end-newline");
let external_command = create_external_command(
engine_state,
stack,
call,
redirect_stdout,
redirect_stderr,
redirect_combine,
trim_end_newline,
)?;
let cwd = current_dir(engine_state, stack)?; let cwd = current_dir(engine_state, stack)?;
let mut command = external_command.spawn_simple_command(&cwd.to_string_lossy())?; let mut command = external_command.spawn_simple_command(&cwd.to_string_lossy())?;
command.current_dir(cwd); command.current_dir(cwd);
command.envs(&external_command.env_vars); command.envs(external_command.env_vars);
let err = command.exec(); // this replaces our process, should not return // this either replaces our process and should not return,
// or the exec fails and we get an error back
exec_impl(command, call.head)
}
#[cfg(unix)]
fn exec_impl(mut command: std::process::Command, span: Span) -> Result<PipelineData, ShellError> {
use std::os::unix::process::CommandExt;
let error = command.exec();
Err(ShellError::GenericError( Err(ShellError::GenericError(
"Error on exec".to_string(), "Error on exec".into(),
err.to_string(), error.to_string(),
Some(name_span), Some(span),
None, None,
Vec::new(), Vec::new(),
)) ))
} }
#[cfg(windows)]
fn exec_impl(mut command: std::process::Command, span: Span) -> Result<PipelineData, ShellError> {
match command.spawn() {
Ok(mut child) => match child.wait() {
Ok(status) => std::process::exit(status.code().unwrap_or(0)),
Err(e) => Err(ShellError::ExternalCommand {
label: "Error in external command".into(),
help: e.to_string(),
span,
}),
},
Err(e) => Err(ShellError::ExternalCommand {
label: "Error spawning external command".into(),
help: e.to_string(),
span,
}),
}
}

View File

@ -1,5 +1,4 @@
mod complete; mod complete;
#[cfg(unix)]
mod exec; mod exec;
mod nu_check; mod nu_check;
#[cfg(any( #[cfg(any(
@ -16,7 +15,6 @@ mod sys;
mod which_; mod which_;
pub use complete::Complete; pub use complete::Complete;
#[cfg(unix)]
pub use exec::Exec; pub use exec::Exec;
pub use nu_check::NuCheck; pub use nu_check::NuCheck;
#[cfg(any( #[cfg(any(

View File

@ -24,7 +24,6 @@ mod echo;
mod empty; mod empty;
mod error_make; mod error_make;
mod every; mod every;
#[cfg(not(windows))]
mod exec; mod exec;
mod export_def; mod export_def;
mod fill; mod fill;