nu_command/platform/
kill.rs

1use nu_engine::command_prelude::*;
2use nu_system::build_kill_command;
3use std::process::Stdio;
4
5#[derive(Clone)]
6pub struct Kill;
7
8impl Command for Kill {
9    fn name(&self) -> &str {
10        "kill"
11    }
12
13    fn description(&self) -> &str {
14        "Kill a process using the process id."
15    }
16
17    fn signature(&self) -> Signature {
18        let signature = Signature::build("kill")
19            .input_output_types(vec![(Type::Nothing, Type::Any)])
20            .allow_variants_without_examples(true)
21            .rest(
22                "pid",
23                SyntaxShape::Int,
24                "Process ids of processes that are to be killed.",
25            )
26            .switch("force", "forcefully kill the process", Some('f'))
27            .switch("quiet", "won't print anything to the console", Some('q'))
28            .category(Category::Platform);
29
30        if cfg!(windows) {
31            return signature;
32        }
33
34        signature.named(
35            "signal",
36            SyntaxShape::Int,
37            "signal decimal number to be sent instead of the default 15 (unsupported on Windows)",
38            Some('s'),
39        )
40    }
41
42    fn search_terms(&self) -> Vec<&str> {
43        vec!["stop", "end", "close"]
44    }
45
46    fn run(
47        &self,
48        engine_state: &EngineState,
49        stack: &mut Stack,
50        call: &Call,
51        _input: PipelineData,
52    ) -> Result<PipelineData, ShellError> {
53        let pids: Vec<i64> = call.rest(engine_state, stack, 0)?;
54        let force: bool = call.has_flag(engine_state, stack, "force")?;
55        let signal: Option<Spanned<i64>> = call.get_flag(engine_state, stack, "signal")?;
56        let quiet: bool = call.has_flag(engine_state, stack, "quiet")?;
57
58        if pids.is_empty() {
59            return Err(ShellError::MissingParameter {
60                param_name: "pid".to_string(),
61                span: call.arguments_span(),
62            });
63        }
64
65        if cfg!(unix) {
66            if let (
67                true,
68                Some(Spanned {
69                    item: _,
70                    span: signal_span,
71                }),
72            ) = (force, signal)
73            {
74                return Err(ShellError::IncompatibleParameters {
75                    left_message: "force".to_string(),
76                    left_span: call
77                        .get_flag_span(stack, "force")
78                        .expect("Had flag force, but didn't have span for flag"),
79                    right_message: "signal".to_string(),
80                    right_span: Span::merge(
81                        call.get_flag_span(stack, "signal")
82                            .expect("Had flag signal, but didn't have span for flag"),
83                        signal_span,
84                    ),
85                });
86            }
87        };
88
89        let mut cmd = build_kill_command(
90            force,
91            pids.iter().copied(),
92            signal.map(|spanned| spanned.item as u32),
93        );
94
95        // pipe everything to null
96        if quiet {
97            cmd.stdin(Stdio::null())
98                .stdout(Stdio::null())
99                .stderr(Stdio::null());
100        }
101
102        let output = cmd.output().map_err(|e| ShellError::GenericError {
103            error: "failed to execute shell command".into(),
104            msg: e.to_string(),
105            span: Some(call.head),
106            help: None,
107            inner: vec![],
108        })?;
109
110        if !quiet && !output.status.success() {
111            return Err(ShellError::GenericError {
112                error: "process didn't terminate successfully".into(),
113                msg: String::from_utf8(output.stderr).unwrap_or_default(),
114                span: Some(call.head),
115                help: None,
116                inner: vec![],
117            });
118        }
119
120        let mut output =
121            String::from_utf8(output.stdout).map_err(|e| ShellError::GenericError {
122                error: "failed to convert output to string".into(),
123                msg: e.to_string(),
124                span: Some(call.head),
125                help: None,
126                inner: vec![],
127            })?;
128
129        output.truncate(output.trim_end().len());
130
131        if output.is_empty() {
132            Ok(Value::nothing(call.head).into_pipeline_data())
133        } else {
134            Ok(Value::string(output, call.head).into_pipeline_data())
135        }
136    }
137
138    fn examples(&self) -> Vec<Example> {
139        vec![
140            Example {
141                description: "Kill the pid using the most memory",
142                example: "ps | sort-by mem | last | kill $in.pid",
143                result: None,
144            },
145            Example {
146                description: "Force kill a given pid",
147                example: "kill --force 12345",
148                result: None,
149            },
150            #[cfg(not(target_os = "windows"))]
151            Example {
152                description: "Send INT signal",
153                example: "kill -s 2 12345",
154                result: None,
155            },
156        ]
157    }
158}
159
160#[cfg(test)]
161mod tests {
162    use super::Kill;
163
164    #[test]
165    fn examples_work_as_expected() {
166        use crate::test_examples;
167        test_examples(Kill {})
168    }
169}