From f1b89bc5758ec6bf58947fcb55d28f6a9de49188 Mon Sep 17 00:00:00 2001 From: Tuomas Tynkkynen Date: Sun, 2 Nov 2025 00:47:27 +0200 Subject: [PATCH] ps: Implement --pid flag --- src/uu/ps/src/process_selection.rs | 11 ++++++++++ src/uu/ps/src/ps.rs | 18 ++++++++++++++- tests/by-util/test_ps.rs | 35 ++++++++++++++++++++++++++++++ 3 files changed, 63 insertions(+), 1 deletion(-) diff --git a/src/uu/ps/src/process_selection.rs b/src/uu/ps/src/process_selection.rs index ebaf8a0..51f770f 100644 --- a/src/uu/ps/src/process_selection.rs +++ b/src/uu/ps/src/process_selection.rs @@ -4,6 +4,7 @@ // file that was distributed with this source code. use clap::ArgMatches; +use std::collections::HashSet; use uu_pgrep::process::{walk_process, ProcessInformation, RunState, Teletype}; use uucore::error::UResult; @@ -46,6 +47,9 @@ pub struct ProcessSelectionSettings { /// - '-x' Lift "must have a tty" restriction. pub dont_require_tty: bool, + /// Select specific process IDs (-p, --pid) + pub pids: Option>, + /// - `-r` Restrict the selection to only running processes. pub only_running: bool, @@ -60,6 +64,9 @@ impl ProcessSelectionSettings { select_non_session_leaders_with_tty: matches.get_flag("a"), select_non_session_leaders: matches.get_flag("d"), dont_require_tty: matches.get_flag("x"), + pids: matches + .get_many::>("pid") + .map(|xs| xs.flatten().copied().collect()), only_running: matches.get_flag("r"), negate_selection: matches.get_flag("deselect"), } @@ -75,6 +82,10 @@ impl ProcessSelectionSettings { return Ok(false); } + if let Some(ref pids) = self.pids { + return Ok(pids.contains(&process.pid)); + } + if self.select_all { return Ok(true); } diff --git a/src/uu/ps/src/ps.rs b/src/uu/ps/src/ps.rs index 25fda82..14bd749 100644 --- a/src/uu/ps/src/ps.rs +++ b/src/uu/ps/src/ps.rs @@ -138,6 +138,15 @@ fn collect_format( Ok(collect) } +fn parse_numeric_list(s: &str) -> Result, String> { + s.split(|c: char| c.is_whitespace() || c == ',') + .map(|word| { + word.parse::() + .map_err(|_| format!("invalid number: '{}'", word)) + }) + .collect() +} + #[allow(clippy::cognitive_complexity)] pub fn uu_app() -> Command { Command::new(uucore::util_name()) @@ -262,6 +271,14 @@ pub fn uu_app() -> Command { .action(ArgAction::SetTrue) .help("do not print header at all"), ) + .arg( + Arg::new("pid") + .short('p') + .long("pid") + .action(ArgAction::Append) + .value_parser(parse_numeric_list) + .help("select by process ID"), + ) // .args([ // Arg::new("command").short('c').help("command name"), // Arg::new("GID") @@ -272,7 +289,6 @@ pub fn uu_app() -> Command { // .short('g') // .long("group") // .help("session or effective group name"), - // Arg::new("PID").short('p').long("pid").help("process id"), // Arg::new("pPID").long("ppid").help("parent process id"), // Arg::new("qPID") // .short('q') diff --git a/tests/by-util/test_ps.rs b/tests/by-util/test_ps.rs index d56a94d..18f2e90 100644 --- a/tests/by-util/test_ps.rs +++ b/tests/by-util/test_ps.rs @@ -202,3 +202,38 @@ fn test_deselect() { .succeeds() .stdout_matches(&Regex::new("\n *1 ").unwrap()); } + +#[test] +#[cfg(target_os = "linux")] +fn test_pid_selection() { + let our_pid = std::process::id(); + // Test that only pid 1 and pid of the test runner is present + let test = |pid_args: &[&str]| { + let match_regex = Regex::new(&format!("^ *1 *\n *{our_pid} *\n$")).unwrap(); + let mut args = vec!["--no-headers", "-o", "pid"]; + args.extend_from_slice(pid_args); + new_ucmd!() + .args(&args) + .succeeds() + .stdout_matches(&match_regex); + }; + + for flag in ["-p", "--pid"] { + test(&[flag, &format!("1 {our_pid}")]); + test(&[flag, &format!("1,{our_pid}")]); + test(&[flag, "1", flag, &our_pid.to_string()]); + } + + // Test nonexistent PID (should show no output) + new_ucmd!() + .args(&["-p", "0", "--no-headers"]) + .fails() + .code_is(1) + .stdout_is(""); + + // Test invalid PID + new_ucmd!() + .args(&["-p", "invalid"]) + .fails() + .stderr_contains("invalid number"); +}