mirror of
https://github.com/uutils/procps.git
synced 2026-06-10 16:14:00 -07:00
ps: Add command ps (#133)
* ps: Add command `ps` * pidof: Add some basic arguments and flags * ps: Introduce new dependencies. Introduce `uu_pgrep` for processing processes, and introduce `prettytable-rs` for output. * ps: Finish command infrastructure. * pgrep: Implemented `Hash` trait for `ProcessInformation`. * misc: Introduce `nix` crate * pgrep: Rename `TerminalType` to `Teletype` * ps: Use `Vec<Rc<RefCell<_>>>` for mutable data. * misc: Fix conflict * misc: Fix fmt * pgrep: Revert refactoring. * ps: Add `basic_collector` * ps: Introduce `picker` mod to do data picking. * ps: Implemented user defined data picking * ps: Collect and apply header mapping and use `Vec` instead of `LinkedList` * ps: Add license header * ps: Apply suggestions * ps: Implemented logic of `-o` * ps: Implement basic function. * ps: Remove empty line of output * ps: Implemented nullable format parser. And also introduce `thiserror` crate. * ps: Implemented `-o` (`--format`) * ps: Add license header for `parser.rs` * misc: Apply modifications from upstream * ps: Implemented code of `time` * ps: Fix check failure * ps: Fix build failure on Windows * ps: Enable tests for another *NIX system. * ps: Revert tests support for *NIX platform. It looks like it's very difficult to implement the ps command on *NIX platforms as portable, as it's only portable if you have a proc pseudo-fs. In other words, the current design only even supports Linux. - FreeBSD's proc pseudo-fs is not automounted. - macOS does not have a proc fs, it use sysctl instead. * ps: Refactor mappings to `mapping.rs` * ps: Clean code * misc: Bump version of `nix` crate. * ps: Rewrite document and optimize code. * ps: Clean TODO and add TODO Remove TODO in `mapping.rs` and add TODO in `collector.rs` * ps: Allow multiple occurrences of the same code. * ps: Fix default codes' display * ps: Use `Result` instead of `Option` in `parser.rs` * ps: Remove duplicate elements of `proc_infos` * ps: Implemented sorting But only implemented the basic usage: sorting by pid. * ps: Rename functions in `sorting.rs` - Rename `sorting` to `sort` - Rename `default_sort` to `sort_by_pid` --------- Co-authored-by: Sylvestre Ledru <sylvestre@debian.org>
This commit is contained in:
Generated
+178
-2
@@ -122,6 +122,12 @@ version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e"
|
||||
|
||||
[[package]]
|
||||
name = "cfg_aliases"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
|
||||
|
||||
[[package]]
|
||||
name = "chrono"
|
||||
version = "0.4.38"
|
||||
@@ -218,6 +224,27 @@ version = "0.8.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345"
|
||||
|
||||
[[package]]
|
||||
name = "csv"
|
||||
version = "1.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ac574ff4d437a7b5ad237ef331c17ccca63c46479e5b5453eb8e10bb99a759fe"
|
||||
dependencies = [
|
||||
"csv-core",
|
||||
"itoa",
|
||||
"ryu",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "csv-core"
|
||||
version = "0.1.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5efa2b3d7902f4b634a20cae3c9c4e6209dc4779feb6863329607560143efa70"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "deranged"
|
||||
version = "0.3.11"
|
||||
@@ -233,6 +260,27 @@ version = "0.1.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8"
|
||||
|
||||
[[package]]
|
||||
name = "dirs-next"
|
||||
version = "2.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"dirs-sys-next",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dirs-sys-next"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"redox_users",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dns-lookup"
|
||||
version = "2.0.4"
|
||||
@@ -251,6 +299,12 @@ version = "1.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "11157ac094ffbdde99aa67b23417ebdd801842852b500e395a45a9c0aac03e4a"
|
||||
|
||||
[[package]]
|
||||
name = "encode_unicode"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0"
|
||||
|
||||
[[package]]
|
||||
name = "errno"
|
||||
version = "0.3.8"
|
||||
@@ -324,6 +378,17 @@ dependencies = [
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "is-terminal"
|
||||
version = "0.4.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f23ff5ef2b80d608d61efee834934d862cd92461afc0560dedf493e4c033738b"
|
||||
dependencies = [
|
||||
"hermit-abi",
|
||||
"libc",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
version = "1.0.11"
|
||||
@@ -339,12 +404,28 @@ dependencies = [
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lazy_static"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.155"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c"
|
||||
|
||||
[[package]]
|
||||
name = "libredox"
|
||||
version = "0.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d"
|
||||
dependencies = [
|
||||
"bitflags 2.5.0",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "linux-raw-sys"
|
||||
version = "0.3.8"
|
||||
@@ -377,7 +458,19 @@ checksum = "ab2156c4fce2f8df6c499cc1c763e4394b7482525bf2a9701c9d79d215f519e4"
|
||||
dependencies = [
|
||||
"bitflags 2.5.0",
|
||||
"cfg-if",
|
||||
"cfg_aliases",
|
||||
"cfg_aliases 0.1.1",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nix"
|
||||
version = "0.29.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46"
|
||||
dependencies = [
|
||||
"bitflags 2.5.0",
|
||||
"cfg-if",
|
||||
"cfg_aliases 0.2.1",
|
||||
"libc",
|
||||
]
|
||||
|
||||
@@ -495,6 +588,20 @@ dependencies = [
|
||||
"yansi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "prettytable-rs"
|
||||
version = "0.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "eea25e07510aa6ab6547308ebe3c036016d162b8da920dbb079e3ba8acf3d95a"
|
||||
dependencies = [
|
||||
"csv",
|
||||
"encode_unicode",
|
||||
"is-terminal",
|
||||
"lazy_static",
|
||||
"term",
|
||||
"unicode-width",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.79"
|
||||
@@ -526,6 +633,7 @@ dependencies = [
|
||||
"uu_pgrep",
|
||||
"uu_pidof",
|
||||
"uu_pmap",
|
||||
"uu_ps",
|
||||
"uu_pwdx",
|
||||
"uu_slabtop",
|
||||
"uu_w",
|
||||
@@ -593,6 +701,17 @@ dependencies = [
|
||||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "redox_users"
|
||||
version = "0.4.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bd283d9651eeda4b2a83a43c1c91b266c40fd76ecd39a50a8c630ae69dc72891"
|
||||
dependencies = [
|
||||
"getrandom",
|
||||
"libredox",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "1.10.6"
|
||||
@@ -664,6 +783,18 @@ dependencies = [
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustversion"
|
||||
version = "1.0.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "955d28af4278de8121b7ebeb796b6a45735dc01436d898801014aced2773a3d6"
|
||||
|
||||
[[package]]
|
||||
name = "ryu"
|
||||
version = "1.0.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f"
|
||||
|
||||
[[package]]
|
||||
name = "same-file"
|
||||
version = "1.0.6"
|
||||
@@ -759,6 +890,17 @@ dependencies = [
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "term"
|
||||
version = "0.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c59df8ac95d96ff9bede18eb7300b0fda5e5d8d90960e76f8e14ae765eedbf1f"
|
||||
dependencies = [
|
||||
"dirs-next",
|
||||
"rustversion",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "terminal_size"
|
||||
version = "0.2.6"
|
||||
@@ -791,6 +933,26 @@ dependencies = [
|
||||
"unicode-width",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "1.0.63"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c0342370b38b6a11b6cc11d6a805569958d54cfa061a29969c3b5ce2ea405724"
|
||||
dependencies = [
|
||||
"thiserror-impl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror-impl"
|
||||
version = "1.0.63"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "time"
|
||||
version = "0.3.36"
|
||||
@@ -885,6 +1047,20 @@ dependencies = [
|
||||
"uucore",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "uu_ps"
|
||||
version = "0.0.1"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"clap",
|
||||
"libc",
|
||||
"nix 0.29.0",
|
||||
"prettytable-rs",
|
||||
"thiserror",
|
||||
"uu_pgrep",
|
||||
"uucore",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "uu_pwdx"
|
||||
version = "0.0.1"
|
||||
@@ -929,7 +1105,7 @@ dependencies = [
|
||||
"dns-lookup",
|
||||
"glob",
|
||||
"libc",
|
||||
"nix",
|
||||
"nix 0.28.0",
|
||||
"number_prefix",
|
||||
"once_cell",
|
||||
"os_display",
|
||||
|
||||
+15
-1
@@ -25,7 +25,17 @@ build = "build.rs"
|
||||
default = ["feat_common_core"]
|
||||
uudoc = []
|
||||
|
||||
feat_common_core = ["pwdx", "free", "w", "watch", "pmap", "slabtop", "pgrep", "pidof"]
|
||||
feat_common_core = [
|
||||
"pwdx",
|
||||
"free",
|
||||
"w",
|
||||
"watch",
|
||||
"pmap",
|
||||
"slabtop",
|
||||
"pgrep",
|
||||
"pidof",
|
||||
"ps",
|
||||
]
|
||||
|
||||
[workspace.dependencies]
|
||||
uucore = "0.0.27"
|
||||
@@ -38,12 +48,15 @@ libc = "0.2.154"
|
||||
phf = "0.11.2"
|
||||
phf_codegen = "0.11.2"
|
||||
textwrap = { version = "0.16.1", features = ["terminal_size"] }
|
||||
thiserror = "1.0.63"
|
||||
xattr = "1.3.1"
|
||||
tempfile = "3.10.1"
|
||||
rand = { version = "0.8.5", features = ["small_rng"] }
|
||||
bytesize = "1.3.0"
|
||||
chrono = { version = "0.4.38", default-features = false, features = ["clock"] }
|
||||
walkdir = "2.5.0"
|
||||
prettytable-rs = "0.10.0"
|
||||
nix = { version = "0.29", default-features = false }
|
||||
|
||||
[dependencies]
|
||||
clap = { workspace = true }
|
||||
@@ -64,6 +77,7 @@ pmap = { optional = true, version = "0.0.1", package = "uu_pmap", path = "src/uu
|
||||
slabtop = { optional = true, version = "0.0.1", package = "uu_slabtop", path = "src/uu/slabtop" }
|
||||
pgrep = { optional = true, version = "0.0.1", package = "uu_pgrep", path = "src/uu/pgrep" }
|
||||
pidof = { optional = true, version = "0.0.1", package = "uu_pidof", path = "src/uu/pidof" }
|
||||
ps = { optional = true, version = "0.0.1", package = "uu_ps", path = "src/uu/ps" }
|
||||
|
||||
[dev-dependencies]
|
||||
pretty_assertions = "1.4.0"
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
[package]
|
||||
name = "uu_ps"
|
||||
version = "0.0.1"
|
||||
edition = "2021"
|
||||
authors = ["uutils developers"]
|
||||
license = "MIT"
|
||||
description = "ps - (uutils) Report a snapshot of the current processes"
|
||||
|
||||
homepage = "https://github.com/uutils/procps"
|
||||
repository = "https://github.com/uutils/procps/tree/main/src/uu/ps"
|
||||
keywords = ["acl", "uutils", "cross-platform", "cli", "utility"]
|
||||
categories = ["command-line-utilities"]
|
||||
|
||||
[dependencies]
|
||||
uucore = { workspace = true, features = ["utmpx"] }
|
||||
clap = { workspace = true }
|
||||
chrono = { workspace = true, default-features = false, features = ["clock"] }
|
||||
libc = { workspace = true }
|
||||
prettytable-rs = { workspace = true }
|
||||
nix = { workspace = true }
|
||||
thiserror = { workspace = true }
|
||||
|
||||
uu_pgrep = { path = "../pgrep" }
|
||||
|
||||
[lib]
|
||||
path = "src/ps.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "ps"
|
||||
path = "src/main.rs"
|
||||
@@ -0,0 +1,7 @@
|
||||
# ps
|
||||
|
||||
```
|
||||
ps [options]
|
||||
```
|
||||
|
||||
Report a snapshot of the current processes.
|
||||
@@ -0,0 +1,109 @@
|
||||
// This file is part of the uutils procps package.
|
||||
//
|
||||
// For the full copyright and license information, please view the LICENSE
|
||||
// file that was distributed with this source code.
|
||||
|
||||
use clap::ArgMatches;
|
||||
#[cfg(target_family = "unix")]
|
||||
use nix::errno::Errno;
|
||||
use std::{cell::RefCell, path::PathBuf, rc::Rc, str::FromStr};
|
||||
use uu_pgrep::process::{ProcessInformation, Teletype};
|
||||
|
||||
// TODO: Temporary add to this file, this function will add to uucore.
|
||||
#[cfg(not(target_os = "redox"))]
|
||||
#[cfg(target_family = "unix")]
|
||||
fn getsid(pid: i32) -> Option<i32> {
|
||||
unsafe {
|
||||
let result = libc::getsid(pid);
|
||||
if Errno::last() == Errno::UnknownErrno {
|
||||
Some(result)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Temporary add to this file, this function will add to uucore.
|
||||
#[cfg(target_family = "windows")]
|
||||
fn getsid(_pid: i32) -> Option<i32> {
|
||||
Some(0)
|
||||
}
|
||||
|
||||
// Guessing it matches the current terminal
|
||||
pub(crate) fn basic_collector(
|
||||
proc_snapshot: &[Rc<RefCell<ProcessInformation>>],
|
||||
) -> Vec<Rc<RefCell<ProcessInformation>>> {
|
||||
let mut result = Vec::new();
|
||||
|
||||
let current_tty = {
|
||||
// SAFETY: The `libc::getpid` always return i32
|
||||
let proc_path =
|
||||
PathBuf::from_str(&format!("/proc/{}/", unsafe { libc::getpid() })).unwrap();
|
||||
let current_proc_info = ProcessInformation::try_new(proc_path).unwrap();
|
||||
|
||||
current_proc_info.tty()
|
||||
};
|
||||
|
||||
for proc_info in proc_snapshot {
|
||||
let proc_ttys = proc_info.borrow().tty();
|
||||
|
||||
if proc_ttys == current_tty {
|
||||
result.push(proc_info.clone())
|
||||
}
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
/// Filter for processes
|
||||
///
|
||||
/// - `-A` Select all processes. Identical to `-e`.
|
||||
pub(crate) fn process_collector(
|
||||
matches: &ArgMatches,
|
||||
proc_snapshot: &[Rc<RefCell<ProcessInformation>>],
|
||||
) -> Vec<Rc<RefCell<ProcessInformation>>> {
|
||||
let mut result = Vec::new();
|
||||
|
||||
// flag `-A`
|
||||
if matches.get_flag("A") {
|
||||
result.extend(proc_snapshot.iter().map(Rc::clone))
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
/// Filter for session
|
||||
///
|
||||
/// - `-d` Select all processes except session leaders.
|
||||
/// - `-a` Select all processes except both session leaders (see getsid(2)) and processes not associated with a terminal.
|
||||
pub(crate) fn session_collector(
|
||||
matches: &ArgMatches,
|
||||
proc_snapshot: &[Rc<RefCell<ProcessInformation>>],
|
||||
) -> Vec<Rc<RefCell<ProcessInformation>>> {
|
||||
let mut result = Vec::new();
|
||||
|
||||
let tty = |proc: &Rc<RefCell<ProcessInformation>>| proc.borrow_mut().tty();
|
||||
|
||||
// flag `-d`
|
||||
// TODO: Implementation this collection, guessing it pid=sid
|
||||
if matches.get_flag("d") {
|
||||
proc_snapshot.iter().for_each(|_| {});
|
||||
}
|
||||
|
||||
// flag `-a`
|
||||
// Guessing it pid=sid, and associated terminal.
|
||||
if matches.get_flag("a") {
|
||||
proc_snapshot.iter().for_each(|it| {
|
||||
let pid = it.borrow().pid;
|
||||
|
||||
if let Some(sid) = getsid(pid as i32) {
|
||||
// Check is session leader
|
||||
if sid != (pid as i32) && tty(it) != Teletype::Unknown {
|
||||
result.push(it.clone())
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
uucore::bin!(uu_ps);
|
||||
@@ -0,0 +1,180 @@
|
||||
// This file is part of the uutils procps package.
|
||||
//
|
||||
// For the full copyright and license information, please view the LICENSE
|
||||
// file that was distributed with this source code.
|
||||
|
||||
use crate::parser::OptionalKeyValue;
|
||||
use std::collections::HashMap;
|
||||
|
||||
pub(crate) fn collect_code_mapping(formats: &[OptionalKeyValue]) -> Vec<(String, String)> {
|
||||
let mapping = default_mapping();
|
||||
|
||||
formats
|
||||
.iter()
|
||||
.map(|it| {
|
||||
let key = it.key().to_string();
|
||||
match it.value() {
|
||||
Some(value) => (key, value.clone()),
|
||||
None => (key.clone(), mapping.get(&key).unwrap().to_string()),
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Returns the default codes.
|
||||
pub(crate) fn default_codes() -> Vec<String> {
|
||||
["pid", "tname", "time", "ucmd"].map(Into::into).to_vec()
|
||||
}
|
||||
|
||||
/// Collect mapping from argument
|
||||
pub(crate) fn default_mapping() -> HashMap<String, String> {
|
||||
let mut mapping = HashMap::new();
|
||||
let mut append = |code: &str, header: &str| mapping.insert(code.into(), header.into());
|
||||
|
||||
// Those mapping generated from manpage
|
||||
append("%cpu", "%CPU");
|
||||
append("%mem", "%MEM");
|
||||
append("ag_id", "AGID");
|
||||
append("ag_nice", "AGNI");
|
||||
append("args", "COMMAND");
|
||||
append("blocked", "BLOCKED");
|
||||
append("bsdstart", "START");
|
||||
append("bsdtime", "TIME");
|
||||
append("c", "C");
|
||||
append("caught", "CAUGHT");
|
||||
append("cgname", "CGNAME");
|
||||
append("cgroup", "CGROUP");
|
||||
append("cgroupns", "CGROUPNS");
|
||||
append("class", "CLS");
|
||||
append("cls", "CLS");
|
||||
append("cmd", "CMD");
|
||||
append("comm", "COMMAND");
|
||||
append("command", "COMMAND");
|
||||
append("cp", "CP");
|
||||
append("cputime", "TIME");
|
||||
append("cputimes", "TIME");
|
||||
append("cuc", "%CUC");
|
||||
append("cuu", "%CUU");
|
||||
append("drs", "DRS");
|
||||
append("egid", "EGID");
|
||||
append("egroup", "EGROUP");
|
||||
append("eip", "EIP");
|
||||
append("esp", "ESP");
|
||||
append("etime", "ELAPSED");
|
||||
append("etimes", "ELAPSED");
|
||||
append("euid", "EUID");
|
||||
append("euser", "EUSER");
|
||||
append("exe", "EXE");
|
||||
append("f", "F");
|
||||
append("fgid", "FGID");
|
||||
append("fgroup", "FGROUP");
|
||||
append("flag", "F");
|
||||
append("flags", "F");
|
||||
append("fname", "COMMAND");
|
||||
append("fuid", "FUID");
|
||||
append("fuser", "FUSER");
|
||||
append("gid", "GID");
|
||||
append("group", "GROUP");
|
||||
append("ignored", "IGNORED");
|
||||
append("ipcns", "IPCNS");
|
||||
append("label", "LABEL");
|
||||
append("lstart", "STARTED");
|
||||
append("lsession", "SESSION");
|
||||
append("luid", "LUID");
|
||||
append("lwp", "LWP");
|
||||
append("lxc", "LXC");
|
||||
append("machine", "MACHINE");
|
||||
append("maj_flt", "MAJFLT");
|
||||
append("min_flt", "MINFLT");
|
||||
append("mntns", "MNTNS");
|
||||
append("netns", "NETNS");
|
||||
append("ni", "NI");
|
||||
append("nice", "NI");
|
||||
append("nlwp", "NLWP");
|
||||
append("numa", "NUMA");
|
||||
append("nwchan", "WCHAN");
|
||||
append("oom", "OOM");
|
||||
append("oomadj", "OOMADJ");
|
||||
append("ouid", "OWNER");
|
||||
append("pcpu", "%CPU");
|
||||
append("pending", "PENDING");
|
||||
append("pgid", "PGID");
|
||||
append("pgrp", "PGRP");
|
||||
append("pid", "PID");
|
||||
append("pidns", "PIDNS");
|
||||
append("pmem", "%MEM");
|
||||
append("policy", "POL");
|
||||
append("ppid", "PPID");
|
||||
append("pri", "PRI");
|
||||
append("psr", "PSR");
|
||||
append("pss", "PSS");
|
||||
append("rbytes", "RBYTES");
|
||||
append("rchars", "RCHARS");
|
||||
append("rgid", "RGID");
|
||||
append("rgroup", "RGROUP");
|
||||
append("rops", "ROPS");
|
||||
append("rss", "RSS");
|
||||
append("rssize", "RSS");
|
||||
append("rsz", "RSZ");
|
||||
append("rtprio", "RTPRIO");
|
||||
append("ruid", "RUID");
|
||||
append("ruser", "RUSER");
|
||||
append("s", "S");
|
||||
append("sched", "SCH");
|
||||
append("seat", "SEAT");
|
||||
append("sess", "SESS");
|
||||
append("sgi_p", "P");
|
||||
append("sgid", "SGID");
|
||||
append("sgroup", "SGROUP");
|
||||
append("sid", "SID");
|
||||
append("sig", "PENDING");
|
||||
append("sigcatch", "CAUGHT");
|
||||
append("sigignore", "IGNORED");
|
||||
append("sigmask", "BLOCKED");
|
||||
append("size", "SIZE");
|
||||
append("slice", "SLICE");
|
||||
append("spid", "SPID");
|
||||
append("stackp", "STACKP");
|
||||
append("start", "STARTED");
|
||||
append("start_time", "START");
|
||||
append("stat", "STAT");
|
||||
append("state", "S");
|
||||
append("stime", "STIME");
|
||||
append("suid", "SUID");
|
||||
append("supgid", "SUPGID");
|
||||
append("supgrp", "SUPGRP");
|
||||
append("suser", "SUSER");
|
||||
append("svgid", "SVGID");
|
||||
append("svuid", "SVUID");
|
||||
append("sz", "SZ");
|
||||
append("tgid", "TGID");
|
||||
append("thcount", "THCNT");
|
||||
append("tid", "TID");
|
||||
append("time", "TIME");
|
||||
append("timens", "TIMENS");
|
||||
append("times", "TIME");
|
||||
append("tname", "TTY");
|
||||
append("tpgid", "TPGID");
|
||||
append("trs", "TRS");
|
||||
append("tt", "TT");
|
||||
append("tty", "TT");
|
||||
append("ucmd", "CMD");
|
||||
append("ucomm", "COMMAND");
|
||||
append("uid", "UID");
|
||||
append("uname", "USER");
|
||||
append("unit", "UNIT");
|
||||
append("user", "USER");
|
||||
append("userns", "USERNS");
|
||||
append("uss", "USS");
|
||||
append("utsns", "UTSNS");
|
||||
append("uunit", "UUNIT");
|
||||
append("vsize", "VSZ");
|
||||
append("vsz", "VSZ");
|
||||
append("wbytes", "WBYTES");
|
||||
append("wcbytes", "WCBYTES");
|
||||
append("wchan", "WCHAN");
|
||||
append("wchars", "WCHARS");
|
||||
append("wops", "WOPS");
|
||||
|
||||
mapping
|
||||
}
|
||||
@@ -0,0 +1,136 @@
|
||||
// This file is part of the uutils procps package.
|
||||
//
|
||||
// For the full copyright and license information, please view the LICENSE
|
||||
// file that was distributed with this source code.
|
||||
|
||||
use std::convert::Infallible;
|
||||
use thiserror::Error as TError;
|
||||
|
||||
#[derive(Debug, TError, PartialEq, Eq)]
|
||||
pub enum Error {
|
||||
#[error("empty value")]
|
||||
EmptyValue,
|
||||
|
||||
#[error("parsing failed")]
|
||||
ParsingFailed,
|
||||
}
|
||||
|
||||
/// Parsing _**optional**_ key-value arguments
|
||||
///
|
||||
/// There are two formats
|
||||
///
|
||||
/// - `cmd` -> key: `cmd`, value: None
|
||||
/// - `cmd=CMD` -> key: `cmd`, value: `CMD`
|
||||
///
|
||||
/// Other formats can also be parsed:
|
||||
///
|
||||
/// - `cmd=` -> key: `cmd`, value: (empty, no space there)
|
||||
/// - `cmd=abcd123~~~~` -> key: `cmd`, value: `abcd123~~~~`
|
||||
/// - `cmd======?` -> key: `cmd`, value: `=====?`
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct OptionalKeyValue {
|
||||
key: String,
|
||||
value: Option<String>,
|
||||
}
|
||||
|
||||
impl OptionalKeyValue {
|
||||
pub fn new<T>(value: T) -> Self
|
||||
where
|
||||
T: Into<String>,
|
||||
{
|
||||
let value: String = value.into();
|
||||
|
||||
if let Some((key, value)) = value.split_once("=") {
|
||||
Self {
|
||||
key: key.into(),
|
||||
value: Some(value.into()),
|
||||
}
|
||||
} else {
|
||||
Self {
|
||||
key: value,
|
||||
value: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_key<T>(key: T) -> Self
|
||||
where
|
||||
T: Into<String>,
|
||||
{
|
||||
Self {
|
||||
key: key.into(),
|
||||
value: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn key(&self) -> &str {
|
||||
&self.key
|
||||
}
|
||||
|
||||
pub fn value(&self) -> &Option<String> {
|
||||
&self.value
|
||||
}
|
||||
|
||||
pub fn is_value_empty(&self) -> bool {
|
||||
self.value.is_none()
|
||||
}
|
||||
|
||||
pub fn try_get<T: std::str::FromStr>(&self) -> Result<T, Error> {
|
||||
let Some(ref value) = self.value else {
|
||||
return Err(Error::EmptyValue);
|
||||
};
|
||||
|
||||
value.parse::<T>().map_err(|_| Error::ParsingFailed)
|
||||
}
|
||||
}
|
||||
|
||||
// clap value parser wrapper
|
||||
pub(crate) fn parser(value: &str) -> Result<OptionalKeyValue, Infallible> {
|
||||
Ok(OptionalKeyValue::new(value))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[inline(always)]
|
||||
fn new<T>(value: T) -> OptionalKeyValue
|
||||
where
|
||||
T: Into<String>,
|
||||
{
|
||||
OptionalKeyValue::new(value)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parsing() {
|
||||
assert!(new("value").is_value_empty());
|
||||
assert!(!new("value=").is_value_empty());
|
||||
assert!(!new("value=v").is_value_empty());
|
||||
assert!(!new("value=?:").is_value_empty());
|
||||
|
||||
assert!(OptionalKeyValue::with_key("key").key().eq("key"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_key() {
|
||||
assert_eq!(new("value").key(), "value");
|
||||
assert_eq!(new("value=").key(), "value");
|
||||
assert_eq!(new("value=?").key(), "value");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_value() {
|
||||
// String test
|
||||
assert_eq!(new("value").try_get::<String>(), Err(Error::EmptyValue));
|
||||
assert_eq!(new("value=").try_get::<String>(), Ok("".into()));
|
||||
assert_eq!(new("value=?").try_get::<String>(), Ok("?".into()));
|
||||
|
||||
// Number test
|
||||
assert_eq!(new("value").try_get::<usize>(), Err(Error::EmptyValue));
|
||||
assert_eq!(new("value=0").try_get::<usize>(), Ok(0));
|
||||
assert_eq!(new("value=0").try_get::<i128>(), Ok(0));
|
||||
assert_eq!(new("value=-1").try_get::<i128>(), Ok(-1));
|
||||
assert_eq!(new("value=0").try_get::<u128>(), Ok(0));
|
||||
assert_eq!(new("value=-1").try_get::<u128>(), Err(Error::ParsingFailed));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
// This file is part of the uutils procps package.
|
||||
//
|
||||
// For the full copyright and license information, please view the LICENSE
|
||||
// file that was distributed with this source code.
|
||||
|
||||
use std::cell::RefCell;
|
||||
|
||||
use chrono::DateTime;
|
||||
use uu_pgrep::process::{ProcessInformation, Teletype};
|
||||
|
||||
pub(crate) fn collect_pickers(
|
||||
code_order: &[String],
|
||||
) -> Vec<Box<dyn Fn(RefCell<ProcessInformation>) -> String>> {
|
||||
let mut pickers = Vec::new();
|
||||
|
||||
for code in code_order {
|
||||
match code.as_str() {
|
||||
"pid" | "tgid" => pickers.push(helper(pid)),
|
||||
"tname" | "tt" | "tty" => pickers.push(helper(tty)),
|
||||
"time" | "cputime" => pickers.push(helper(time)),
|
||||
"ucmd" => pickers.push(helper(ucmd)),
|
||||
"cmd" => pickers.push(helper(cmd)),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
pickers
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn helper(
|
||||
f: impl Fn(RefCell<ProcessInformation>) -> String + 'static,
|
||||
) -> Box<dyn Fn(RefCell<ProcessInformation>) -> String> {
|
||||
Box::new(f)
|
||||
}
|
||||
|
||||
fn pid(proc_info: RefCell<ProcessInformation>) -> String {
|
||||
format!("{}", proc_info.borrow().pid)
|
||||
}
|
||||
|
||||
fn tty(proc_info: RefCell<ProcessInformation>) -> String {
|
||||
match proc_info.borrow().tty() {
|
||||
Teletype::Tty(tty) => format!("tty{}", tty),
|
||||
Teletype::TtyS(ttys) => format!("ttyS{}", ttys),
|
||||
Teletype::Pts(pts) => format!("pts/{}", pts),
|
||||
Teletype::Unknown => "?".to_owned(),
|
||||
}
|
||||
}
|
||||
|
||||
fn time(proc_info: RefCell<ProcessInformation>) -> String {
|
||||
// https://docs.kernel.org/filesystems/proc.html#id10
|
||||
// Index of 13 14
|
||||
|
||||
let cumulative_cpu_time = {
|
||||
let utime = proc_info.borrow_mut().stat()[13].parse::<i64>().unwrap();
|
||||
let stime = proc_info.borrow_mut().stat()[14].parse::<i64>().unwrap();
|
||||
utime + stime
|
||||
};
|
||||
|
||||
DateTime::from_timestamp_millis(cumulative_cpu_time)
|
||||
.unwrap()
|
||||
.format("%H:%M:%S")
|
||||
.to_string()
|
||||
}
|
||||
|
||||
fn cmd(proc_info: RefCell<ProcessInformation>) -> String {
|
||||
proc_info.borrow().cmdline.clone()
|
||||
}
|
||||
|
||||
fn ucmd(proc_info: RefCell<ProcessInformation>) -> String {
|
||||
proc_info.borrow_mut().status().get("Name").unwrap().into()
|
||||
}
|
||||
@@ -0,0 +1,206 @@
|
||||
// This file is part of the uutils procps package.
|
||||
//
|
||||
// For the full copyright and license information, please view the LICENSE
|
||||
// file that was distributed with this source code.
|
||||
|
||||
mod collector;
|
||||
mod mapping;
|
||||
mod parser;
|
||||
mod picker;
|
||||
mod sorting;
|
||||
|
||||
use clap::crate_version;
|
||||
use clap::{Arg, ArgAction, ArgMatches, Command};
|
||||
use mapping::{collect_code_mapping, default_codes, default_mapping};
|
||||
use parser::{parser, OptionalKeyValue};
|
||||
use prettytable::{format::consts::FORMAT_CLEAN, Row, Table};
|
||||
use std::{cell::RefCell, rc::Rc};
|
||||
use uu_pgrep::process::walk_process;
|
||||
use uucore::{
|
||||
error::{UError, UResult, USimpleError},
|
||||
format_usage, help_about, help_usage,
|
||||
};
|
||||
|
||||
const ABOUT: &str = help_about!("ps.md");
|
||||
const USAGE: &str = help_usage!("ps.md");
|
||||
|
||||
#[uucore::main]
|
||||
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
||||
let matches = uu_app().try_get_matches_from(args)?;
|
||||
|
||||
let snapshot = walk_process()
|
||||
.map(|it| Rc::new(RefCell::new(it)))
|
||||
.collect::<Vec<_>>();
|
||||
let mut proc_infos = Vec::new();
|
||||
|
||||
proc_infos.extend(collector::basic_collector(&snapshot));
|
||||
proc_infos.extend(collector::process_collector(&matches, &snapshot));
|
||||
proc_infos.extend(collector::session_collector(&matches, &snapshot));
|
||||
|
||||
proc_infos.dedup_by(|a, b| a.borrow().pid == b.borrow().pid);
|
||||
|
||||
sorting::sort(&mut proc_infos, &matches);
|
||||
|
||||
let arg_formats = collect_format(&matches);
|
||||
let Ok(arg_formats) = arg_formats else {
|
||||
return Err(arg_formats.err().unwrap());
|
||||
};
|
||||
|
||||
// Collect codes with order
|
||||
let codes = if arg_formats.is_empty() {
|
||||
default_codes()
|
||||
} else {
|
||||
arg_formats.iter().map(|it| it.key().to_owned()).collect()
|
||||
};
|
||||
|
||||
// Collect pickers ordered by codes
|
||||
let pickers = picker::collect_pickers(&codes);
|
||||
|
||||
// Constructing table
|
||||
let mut rows = Vec::new();
|
||||
for proc in proc_infos {
|
||||
let picked = pickers
|
||||
.iter()
|
||||
.map(|picker| picker(Rc::unwrap_or_clone(proc.clone())));
|
||||
rows.push(Row::from_iter(picked));
|
||||
}
|
||||
|
||||
// Apply header mapping
|
||||
let code_mapping = if arg_formats.is_empty() {
|
||||
let default_mapping = default_mapping();
|
||||
default_codes();
|
||||
codes
|
||||
.into_iter()
|
||||
.map(|code| (code.clone(), default_mapping[&code].to_string()))
|
||||
.collect::<Vec<_>>()
|
||||
} else {
|
||||
collect_code_mapping(&arg_formats)
|
||||
};
|
||||
|
||||
let header = code_mapping
|
||||
.iter()
|
||||
.map(|(_, header)| header)
|
||||
.map(Into::into)
|
||||
.collect::<Vec<String>>();
|
||||
|
||||
// Apply header
|
||||
let mut table = Table::from_iter([Row::from_iter(header)]);
|
||||
table.set_format(*FORMAT_CLEAN);
|
||||
table.extend(rows);
|
||||
|
||||
print!("{}", table);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn collect_format(
|
||||
matches: &ArgMatches,
|
||||
) -> Result<Vec<OptionalKeyValue>, Box<dyn UError + 'static>> {
|
||||
let arg_format = matches.get_many::<OptionalKeyValue>("format");
|
||||
|
||||
let collect = arg_format.unwrap_or_default().cloned().collect::<Vec<_>>();
|
||||
|
||||
let default_mapping = default_mapping();
|
||||
|
||||
// Validate key is exist
|
||||
for key in collect.iter().map(OptionalKeyValue::key) {
|
||||
if !default_mapping.contains_key(key) {
|
||||
return Err(USimpleError::new(
|
||||
1,
|
||||
format!("error: unknown user-defined format specifier \"{key}\""),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(collect)
|
||||
}
|
||||
|
||||
#[allow(clippy::cognitive_complexity)]
|
||||
pub fn uu_app() -> Command {
|
||||
Command::new(uucore::util_name())
|
||||
.version(crate_version!())
|
||||
.about(ABOUT)
|
||||
.override_usage(format_usage(USAGE))
|
||||
.infer_long_args(true)
|
||||
.disable_help_flag(true)
|
||||
.arg(Arg::new("help").long("help").action(ArgAction::Help))
|
||||
.args([
|
||||
Arg::new("A")
|
||||
.short('A')
|
||||
.help("all processes")
|
||||
.visible_alias("e")
|
||||
.action(ArgAction::SetTrue),
|
||||
Arg::new("a")
|
||||
.short('a')
|
||||
.help("all with tty, except session leaders")
|
||||
.action(ArgAction::SetTrue),
|
||||
// Arg::new("a_")
|
||||
// .short('a')
|
||||
// .help("all with tty, including other users")
|
||||
// .action(ArgAction::SetTrue)
|
||||
// .allow_hyphen_values(true),
|
||||
Arg::new("d")
|
||||
.short('d')
|
||||
.help("all except session leaders")
|
||||
.action(ArgAction::SetTrue),
|
||||
Arg::new("deselect")
|
||||
.long("deselect")
|
||||
.short('N')
|
||||
.help("negate selection")
|
||||
.action(ArgAction::SetTrue),
|
||||
// Arg::new("r")
|
||||
// .short('r')
|
||||
// .action(ArgAction::SetTrue)
|
||||
// .help("only running processes")
|
||||
// .allow_hyphen_values(true),
|
||||
// Arg::new("T")
|
||||
// .short('T')
|
||||
// .action(ArgAction::SetTrue)
|
||||
// .help("all processes on this terminal")
|
||||
// .allow_hyphen_values(true),
|
||||
// Arg::new("x")
|
||||
// .short('x')
|
||||
// .action(ArgAction::SetTrue)
|
||||
// .help("processes without controlling ttys")
|
||||
// .allow_hyphen_values(true),
|
||||
])
|
||||
.arg(
|
||||
Arg::new("format")
|
||||
.short('o')
|
||||
.long("format")
|
||||
.action(ArgAction::Append)
|
||||
.value_delimiter(',')
|
||||
.value_parser(parser)
|
||||
.help("user-defined format"),
|
||||
)
|
||||
// .args([
|
||||
// Arg::new("command").short('c').help("command name"),
|
||||
// Arg::new("GID")
|
||||
// .short('G')
|
||||
// .long("Group")
|
||||
// .help("real group id or name"),
|
||||
// Arg::new("group")
|
||||
// .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')
|
||||
// .long("quick-pid")
|
||||
// .help("process id"),
|
||||
// Arg::new("session")
|
||||
// .short('s')
|
||||
// .long("sid")
|
||||
// .help("session id"),
|
||||
// Arg::new("t").short('t').long("tty").help("terminal"),
|
||||
// Arg::new("eUID")
|
||||
// .short('u')
|
||||
// .long("user")
|
||||
// .help("effective user id or name"),
|
||||
// Arg::new("rUID")
|
||||
// .short('U')
|
||||
// .long("User")
|
||||
// .help("real user id or name"),
|
||||
// ])
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
// This file is part of the uutils procps package.
|
||||
//
|
||||
// For the full copyright and license information, please view the LICENSE
|
||||
// file that was distributed with this source code.
|
||||
|
||||
use clap::ArgMatches;
|
||||
use std::{cell::RefCell, rc::Rc};
|
||||
use uu_pgrep::process::ProcessInformation;
|
||||
|
||||
// TODO: Implementing sorting flags.
|
||||
pub(crate) fn sort(input: &mut [Rc<RefCell<ProcessInformation>>], _matches: &ArgMatches) {
|
||||
sort_by_pid(input)
|
||||
}
|
||||
|
||||
/// Sort by pid. (Default)
|
||||
fn sort_by_pid(input: &mut [Rc<RefCell<ProcessInformation>>]) {
|
||||
input.sort_by(|a, b| a.borrow().pid.cmp(&b.borrow().pid))
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
// This file is part of the uutils procps package.
|
||||
//
|
||||
// For the full copyright and license information, please view the LICENSE
|
||||
// file that was distributed with this source code.
|
||||
|
||||
use crate::common::util::TestScenario;
|
||||
|
||||
#[test]
|
||||
fn test_invalid_arg() {
|
||||
new_ucmd!().arg("--definitely-invalid").fails().code_is(1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(target_os = "linux")]
|
||||
fn test_code_mapping() {
|
||||
new_ucmd!()
|
||||
.args(&["-o", "cmd=CCMD"])
|
||||
.succeeds()
|
||||
.stdout_contains("CCMD");
|
||||
|
||||
new_ucmd!().args(&["-o", "cmd= "]).succeeds();
|
||||
|
||||
new_ucmd!().args(&["-o", "ccmd=CCMD"]).fails().code_is(1);
|
||||
|
||||
new_ucmd!()
|
||||
.args(&["-o", "cmd=CMD1", "-o", "cmd=CMD2"])
|
||||
.succeeds()
|
||||
.stdout_contains("CMD1")
|
||||
.stdout_contains("CMD2");
|
||||
|
||||
new_ucmd!()
|
||||
.args(&["-o", "cmd=CMD1,cmd=CMD2"])
|
||||
.succeeds()
|
||||
.stdout_contains("CMD1")
|
||||
.stdout_contains("CMD2");
|
||||
|
||||
new_ucmd!()
|
||||
.args(&["-o", "ucmd=CMD1", "-o", "ucmd=CMD2"])
|
||||
.succeeds()
|
||||
.stdout_contains("CMD1")
|
||||
.stdout_contains("CMD2");
|
||||
|
||||
new_ucmd!()
|
||||
.args(&["-o", "ucmd=CMD1,ucmd=CMD2"])
|
||||
.succeeds()
|
||||
.stdout_contains("CMD1")
|
||||
.stdout_contains("CMD2");
|
||||
}
|
||||
@@ -36,3 +36,7 @@ mod test_pgrep;
|
||||
#[cfg(feature = "pidof")]
|
||||
#[path = "by-util/test_pidof.rs"]
|
||||
mod test_pidof;
|
||||
|
||||
#[cfg(feature = "ps")]
|
||||
#[path = "by-util/test_ps.rs"]
|
||||
mod test_ps;
|
||||
|
||||
Reference in New Issue
Block a user