diff --git a/crates/nu-command/src/default_context.rs b/crates/nu-command/src/default_context.rs index e693788053..2b2c1b57ab 100644 --- a/crates/nu-command/src/default_context.rs +++ b/crates/nu-command/src/default_context.rs @@ -82,6 +82,7 @@ pub fn create_default_context() -> EngineState { IntoFilesize, IntoInt, IntoString, + Kill, Last, Length, Let, diff --git a/crates/nu-command/src/platform/kill.rs b/crates/nu-command/src/platform/kill.rs new file mode 100644 index 0000000000..9ab4f07409 --- /dev/null +++ b/crates/nu-command/src/platform/kill.rs @@ -0,0 +1,149 @@ +use nu_engine::CallExt; +use nu_protocol::engine::{Command, EngineState, Stack}; +use nu_protocol::{ast::Call, span}; +use nu_protocol::{ + Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Spanned, SyntaxShape, + Value, +}; +use std::process::{Command as CommandSys, Stdio}; + +#[derive(Clone)] +pub struct Kill; + +impl Command for Kill { + fn name(&self) -> &str { + "kill" + } + + fn usage(&self) -> &str { + "Kill a process using the process id." + } + + fn signature(&self) -> Signature { + let signature = Signature::build("kill") + .required( + "pid", + SyntaxShape::Int, + "process id of process that is to be killed", + ) + .rest("rest", SyntaxShape::Int, "rest of processes to kill") + .switch("force", "forcefully kill the process", Some('f')) + .switch("quiet", "won't print anything to the console", Some('q')) + .category(Category::Platform); + + if cfg!(windows) { + return signature; + } + + signature.named( + "signal", + SyntaxShape::Int, + "signal decimal number to be sent instead of the default 15 (unsupported on Windows)", + Some('s'), + ) + } + + fn run( + &self, + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, + _input: PipelineData, + ) -> Result { + let pid: i64 = call.req(engine_state, stack, 0)?; + let rest: Vec = call.rest(engine_state, stack, 1)?; + let force: bool = call.has_flag("force"); + let signal: Option> = call.get_flag(engine_state, stack, "signal")?; + let quiet: bool = call.has_flag("quiet"); + + let mut cmd = if cfg!(windows) { + let mut cmd = CommandSys::new("taskkill"); + + if force { + cmd.arg("/F"); + } + + cmd.arg("/PID"); + cmd.arg(pid.to_string()); + + // each pid must written as `/PID 0` otherwise + // taskkill will act as `killall` unix command + for id in &rest { + cmd.arg("/PID"); + cmd.arg(id.to_string()); + } + + cmd + } else { + let mut cmd = CommandSys::new("kill"); + if force { + if let Some(Spanned { + item: _, + span: signal_span, + }) = signal + { + return Err(ShellError::IncompatibleParameters { + left_message: "force".to_string(), + left_span: call.get_named_arg("force").unwrap().span, + right_message: "signal".to_string(), + right_span: span(&[ + call.get_named_arg("signal").unwrap().span, + signal_span, + ]), + }); + } + cmd.arg("-9"); + } else if let Some(signal_value) = signal { + cmd.arg(format!("-{}", signal_value.item.to_string())); + } + + cmd.arg(pid.to_string()); + + cmd.args(rest.iter().map(move |id| id.to_string())); + + cmd + }; + + // pipe everything to null + if quiet { + cmd.stdin(Stdio::null()) + .stdout(Stdio::null()) + .stderr(Stdio::null()); + } + + cmd.status().expect("failed to execute shell command"); + + Ok(Value::Nothing { span: call.head }.into_pipeline_data()) + } + + fn examples(&self) -> Vec { + vec![ + Example { + description: "Kill the pid using the most memory", + example: "ps | sort-by mem | last | kill $it.pid", + result: None, + }, + Example { + description: "Force kill a given pid", + example: "kill --force 12345", + result: None, + }, + Example { + description: "Send INT signal", + example: "kill -s 2 12345", + result: None, + }, + ] + } +} + +#[cfg(test)] +mod tests { + use super::Kill; + + #[test] + fn examples_work_as_expected() { + use crate::test_examples; + test_examples(Kill {}) + } +} diff --git a/crates/nu-command/src/platform/mod.rs b/crates/nu-command/src/platform/mod.rs index 0a8fc97265..bb50c304f3 100644 --- a/crates/nu-command/src/platform/mod.rs +++ b/crates/nu-command/src/platform/mod.rs @@ -1,5 +1,7 @@ mod clear; +mod kill; mod sleep; pub use clear::Clear; +pub use kill::Kill; pub use sleep::Sleep; diff --git a/crates/nu-protocol/src/ast/call.rs b/crates/nu-protocol/src/ast/call.rs index 815e18a649..f2f9a9c431 100644 --- a/crates/nu-protocol/src/ast/call.rs +++ b/crates/nu-protocol/src/ast/call.rs @@ -46,6 +46,16 @@ impl Call { None } + pub fn get_named_arg(&self, flag_name: &str) -> Option> { + for name in &self.named { + if flag_name == name.0.item { + return Some(name.0.clone()); + } + } + + None + } + pub fn nth(&self, pos: usize) -> Option { self.positional.get(pos).cloned() }