mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2024-11-25 17:10:23 +00:00
ps: Add the -o
option to specify a user-defined column format
This option allows the user to change which colums are displayed by giving comma or space separated list of column format specifiers. A column format specifier is of the form: `COLUMN_NAME[=COLUMN_TITLE]`. Where `COLUMN_NAME` is any of: uid, pid, ppid, pgid, sid, state, tty, or cmd. Specifying a `COLUMN_TITLE` will change the name shown in the column header. `COLUMN_TITLE` may be blank. If all given column titles are blank, the header is omitted.
This commit is contained in:
parent
cdb15a20ff
commit
73a6f2e9ed
Notes:
sideshowbarker
2024-07-17 14:36:19 +09:00
Author: https://github.com/tcl3 Commit: https://github.com/SerenityOS/serenity/commit/73a6f2e9ed Pull-request: https://github.com/SerenityOS/serenity/pull/19974 Reviewed-by: https://github.com/AtkinsSJ
2 changed files with 178 additions and 65 deletions
|
@ -5,7 +5,7 @@ ps - list currently running processes
|
|||
## Synopsis
|
||||
|
||||
```**sh
|
||||
$ ps [--version] [-a] [-A] [-e] [-f] [-p pid-list] [--ppid pid-list] [-q pid-list] [-t tty-list] [-u user-list]
|
||||
$ ps [--version] [-a] [-A] [-e] [-f] [-o column-format] [-p pid-list] [--ppid pid-list] [-q pid-list] [-t tty-list] [-u user-list]
|
||||
```
|
||||
|
||||
## Description
|
||||
|
@ -18,6 +18,14 @@ For each process, print its PID (process ID), to which TTY it belongs, and invok
|
|||
* `-a`: Consider all processes that are associated with a TTY.
|
||||
* `-A` or `-e`: Consider all processes, not just those in the current TTY.
|
||||
* `-f`: Also print for each process: UID (as resolved username), PPID (parent PID), and STATE (Runnable, Sleeping, Selecting, Reading, etc.)
|
||||
* `-o column-format`: Specify a user-defined format, as a list of column format specifiers separated by commas or spaces.
|
||||
|
||||
A column format specifier is of the form: `COLUMN_NAME[=COLUMN_TITLE]`.
|
||||
Where `COLUMN_NAME` is any of the following: `uid`, `pid`, `ppid`, `pgid`, `sid`, `state`, `tty`, or `cmd`.
|
||||
|
||||
Specifying a `COLUMN_TITLE` will change the name shown in the column header. `COLUMN_TITLE` may be blank.
|
||||
If all given column titles are blank, the column header is omitted.
|
||||
|
||||
* `-p pid-list`: Select processes matching any of the given PIDs. `pid-list` is a list of PIDs, separated by commas or spaces.
|
||||
* `--ppid pid-list`: Select processes whose PPID matches any of the given PIDs. `pid-list` is a list of PIDs, separated by commas or spaces.
|
||||
* `-q pid-list`: Only consider the given PIDs, if they exist. Output the processes in the order provided by `pid-list`. `pid-list` is a list of PIDs, separated by commas or spaces.
|
||||
|
@ -26,10 +34,30 @@ For each process, print its PID (process ID), to which TTY it belongs, and invok
|
|||
|
||||
## Examples
|
||||
|
||||
Show all processes (full format):
|
||||
|
||||
```sh
|
||||
$ ps -ef
|
||||
```
|
||||
|
||||
Show the PID, state and name of all processes
|
||||
|
||||
```sh
|
||||
$ ps -eo pid,state,cmd
|
||||
```
|
||||
|
||||
Show the name and state of PID 42 and rename the first column from CMD to Command:
|
||||
|
||||
```sh
|
||||
$ ps -q 42 -o cmd=Command,state
|
||||
```
|
||||
|
||||
Show name of PID 42 and omit the header entirely
|
||||
|
||||
```sh
|
||||
$ ps -q 42 -o cmd=
|
||||
```
|
||||
|
||||
## See Also
|
||||
* [`pmap`(1)](help://man/1/pmap)
|
||||
* [`lsof`(1)](help://man/1/lsof)
|
||||
|
|
|
@ -16,6 +16,113 @@
|
|||
#include <sys/sysmacros.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#define ENUMERATE_COLUMN_DESCRIPTIONS \
|
||||
COLUMN(UserId, "uid", "UID", Alignment::Left) \
|
||||
COLUMN(ProcessId, "pid", "PID", Alignment::Right) \
|
||||
COLUMN(ParentProcessId, "ppid", "PPID", Alignment::Right) \
|
||||
COLUMN(ProcessGroupId, "pgid", "PGID", Alignment::Right) \
|
||||
COLUMN(SessionId, "sid", "SID", Alignment::Right) \
|
||||
COLUMN(State, "state", "STATE", Alignment::Left) \
|
||||
COLUMN(TTY, "tty", "TTY", Alignment::Left) \
|
||||
COLUMN(Command, "cmd", "CMD", Alignment::Left)
|
||||
|
||||
enum class ColumnId {
|
||||
#define COLUMN(column_id, lookup_name, default_title, alignment) column_id,
|
||||
ENUMERATE_COLUMN_DESCRIPTIONS
|
||||
#undef COLUMN
|
||||
__Count
|
||||
};
|
||||
|
||||
enum class Alignment {
|
||||
Left,
|
||||
Right,
|
||||
};
|
||||
|
||||
struct ColumnDescription {
|
||||
StringView lookup_name;
|
||||
StringView default_title;
|
||||
Alignment alignment;
|
||||
};
|
||||
|
||||
struct Column {
|
||||
ColumnId id;
|
||||
String title;
|
||||
Alignment alignment;
|
||||
int width { 0 };
|
||||
String buffer {};
|
||||
};
|
||||
|
||||
static Optional<ColumnId> column_name_to_id(StringView column_name)
|
||||
{
|
||||
#define COLUMN(column_id, lookup_name, default_title, alignment) \
|
||||
if (column_name == lookup_name) \
|
||||
return ColumnId::column_id;
|
||||
ENUMERATE_COLUMN_DESCRIPTIONS
|
||||
#undef COLUMN
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
static ErrorOr<Column> column_from_id(ColumnId column_id, Optional<String> const& custom_title = {})
|
||||
{
|
||||
constexpr Array<ColumnDescription, static_cast<size_t>(ColumnId::__Count)> available_columns { {
|
||||
#define COLUMN(column_id, lookup_name, default_title, alignment) { lookup_name##sv, default_title##sv, alignment },
|
||||
ENUMERATE_COLUMN_DESCRIPTIONS
|
||||
#undef COLUMN
|
||||
} };
|
||||
|
||||
auto const& column_description = available_columns[static_cast<size_t>(column_id)];
|
||||
auto title = custom_title.has_value()
|
||||
? custom_title.value()
|
||||
: TRY(String::from_utf8(column_description.default_title));
|
||||
|
||||
return Column { column_id, title, column_description.alignment };
|
||||
}
|
||||
|
||||
static ErrorOr<String> column_to_string(ColumnId column_id, Core::ProcessStatistics process)
|
||||
{
|
||||
switch (column_id) {
|
||||
case ColumnId::UserId:
|
||||
return String::from_deprecated_string(process.username);
|
||||
case ColumnId::ProcessId:
|
||||
return String::number(process.pid);
|
||||
case ColumnId::ParentProcessId:
|
||||
return String::number(process.ppid);
|
||||
case ColumnId::ProcessGroupId:
|
||||
return String::number(process.pgid);
|
||||
case ColumnId::SessionId:
|
||||
return String::number(process.sid);
|
||||
case ColumnId::TTY:
|
||||
return process.tty == "" ? "n/a"_short_string : String::from_deprecated_string(process.tty);
|
||||
case ColumnId::State:
|
||||
return process.threads.is_empty()
|
||||
? "Zombie"_short_string
|
||||
: String::from_deprecated_string(process.threads.first().state);
|
||||
case ColumnId::Command:
|
||||
return String::from_deprecated_string(process.name);
|
||||
default:
|
||||
VERIFY_NOT_REACHED();
|
||||
}
|
||||
}
|
||||
|
||||
static ErrorOr<Column> parse_column_format_specifier(StringView column_format_specifier)
|
||||
{
|
||||
auto column_specification_parts = column_format_specifier.split_view('=', SplitBehavior::KeepEmpty);
|
||||
if (column_specification_parts.size() > 2)
|
||||
return Error::from_string_literal("Invalid column specifier format");
|
||||
|
||||
auto column_name = column_specification_parts[0];
|
||||
auto maybe_column_id = column_name_to_id(column_name);
|
||||
if (!maybe_column_id.has_value())
|
||||
return Error::from_string_literal("Unknown column");
|
||||
|
||||
Optional<String> column_title;
|
||||
if (column_specification_parts.size() == 2)
|
||||
column_title = TRY(String::from_utf8(column_specification_parts[1]));
|
||||
|
||||
return column_from_id(maybe_column_id.value(), column_title);
|
||||
}
|
||||
|
||||
static ErrorOr<Optional<String>> tty_stat_to_pseudo_name(struct stat tty_stat)
|
||||
{
|
||||
int tty_device_major = major(tty_stat.st_rdev);
|
||||
|
@ -103,23 +210,12 @@ ErrorOr<int> serenity_main(Main::Arguments arguments)
|
|||
TRY(Core::System::unveil("/dev/", "r"));
|
||||
TRY(Core::System::unveil(nullptr, nullptr));
|
||||
|
||||
enum class Alignment {
|
||||
Left,
|
||||
Right,
|
||||
};
|
||||
|
||||
struct Column {
|
||||
String title;
|
||||
Alignment alignment { Alignment::Left };
|
||||
int width { 0 };
|
||||
String buffer;
|
||||
};
|
||||
|
||||
bool every_process_flag = false;
|
||||
bool every_terminal_process_flag = false;
|
||||
bool full_format_flag = false;
|
||||
bool provided_filtering_option = false;
|
||||
bool provided_quick_pid_list = false;
|
||||
Vector<Column> columns;
|
||||
Vector<pid_t> pid_list;
|
||||
Vector<pid_t> parent_pid_list;
|
||||
Vector<DeprecatedString> tty_list;
|
||||
|
@ -130,6 +226,14 @@ ErrorOr<int> serenity_main(Main::Arguments arguments)
|
|||
args_parser.add_option(every_process_flag, "Show every process", nullptr, 'A');
|
||||
args_parser.add_option(every_process_flag, "Show every process (Equivalent to -A)", nullptr, 'e');
|
||||
args_parser.add_option(full_format_flag, "Full format", nullptr, 'f');
|
||||
args_parser.add_option(make_list_option(columns, "Specify a user-defined format.", nullptr, 'o', "column-format", [&](StringView column_format_specifier) -> Optional<Column> {
|
||||
auto column_or_error = parse_column_format_specifier(column_format_specifier);
|
||||
if (column_or_error.is_error()) {
|
||||
warnln("Could not parse '{}' as a column format specifier", column_format_specifier);
|
||||
return {};
|
||||
}
|
||||
return column_or_error.release_value();
|
||||
}));
|
||||
args_parser.add_option(make_list_option(pid_list, "Show processes with a matching PID. (Comma- or space-separated list)", nullptr, 'p', "pid-list", [&](StringView pid_string) {
|
||||
provided_filtering_option = true;
|
||||
auto pid = pid_string.to_int();
|
||||
|
@ -180,37 +284,29 @@ ErrorOr<int> serenity_main(Main::Arguments arguments)
|
|||
return 1;
|
||||
}
|
||||
|
||||
Vector<Column> columns;
|
||||
|
||||
Optional<size_t> uid_column;
|
||||
Optional<size_t> pid_column;
|
||||
Optional<size_t> ppid_column;
|
||||
Optional<size_t> pgid_column;
|
||||
Optional<size_t> sid_column;
|
||||
Optional<size_t> state_column;
|
||||
Optional<size_t> tty_column;
|
||||
Optional<size_t> cmd_column;
|
||||
|
||||
auto add_column = [&](auto title, auto alignment) {
|
||||
columns.unchecked_append({ title, alignment, 0, {} });
|
||||
return columns.size() - 1;
|
||||
if (columns.is_empty()) {
|
||||
auto add_default_column = [&](ColumnId column_id) -> ErrorOr<void> {
|
||||
auto column = TRY(column_from_id(column_id));
|
||||
columns.unchecked_append(move(column));
|
||||
return {};
|
||||
};
|
||||
|
||||
if (full_format_flag) {
|
||||
TRY(columns.try_ensure_capacity(8));
|
||||
uid_column = add_column("UID"_short_string, Alignment::Left);
|
||||
pid_column = add_column("PID"_short_string, Alignment::Right);
|
||||
ppid_column = add_column("PPID"_short_string, Alignment::Right);
|
||||
pgid_column = add_column("PGID"_short_string, Alignment::Right);
|
||||
sid_column = add_column("SID"_short_string, Alignment::Right);
|
||||
state_column = add_column("STATE"_short_string, Alignment::Left);
|
||||
tty_column = add_column("TTY"_short_string, Alignment::Left);
|
||||
cmd_column = add_column("CMD"_short_string, Alignment::Left);
|
||||
TRY(add_default_column(ColumnId::UserId));
|
||||
TRY(add_default_column(ColumnId::ProcessId));
|
||||
TRY(add_default_column(ColumnId::ParentProcessId));
|
||||
TRY(add_default_column(ColumnId::ProcessGroupId));
|
||||
TRY(add_default_column(ColumnId::SessionId));
|
||||
TRY(add_default_column(ColumnId::State));
|
||||
TRY(add_default_column(ColumnId::TTY));
|
||||
TRY(add_default_column(ColumnId::Command));
|
||||
} else {
|
||||
TRY(columns.try_ensure_capacity(3));
|
||||
pid_column = add_column("PID"_short_string, Alignment::Right);
|
||||
tty_column = add_column("TTY"_short_string, Alignment::Left);
|
||||
cmd_column = add_column("CMD"_short_string, Alignment::Left);
|
||||
TRY(add_default_column(ColumnId::ProcessId));
|
||||
TRY(add_default_column(ColumnId::TTY));
|
||||
TRY(add_default_column(ColumnId::Command));
|
||||
}
|
||||
}
|
||||
|
||||
auto all_processes = TRY(Core::ProcessStatisticsReader::get_all());
|
||||
|
@ -252,32 +348,21 @@ ErrorOr<int> serenity_main(Main::Arguments arguments)
|
|||
|
||||
Vector<String> header;
|
||||
TRY(header.try_ensure_capacity(columns.size()));
|
||||
for (auto& column : columns)
|
||||
auto header_is_empty = true;
|
||||
for (auto& column : columns) {
|
||||
if (!column.title.is_empty())
|
||||
header_is_empty = false;
|
||||
header.unchecked_append(column.title);
|
||||
}
|
||||
|
||||
if (!header_is_empty)
|
||||
rows.unchecked_append(move(header));
|
||||
|
||||
for (auto const& process : processes) {
|
||||
Vector<String> row;
|
||||
TRY(row.try_resize(columns.size()));
|
||||
|
||||
if (uid_column.has_value())
|
||||
row[*uid_column] = TRY(String::from_deprecated_string(process.username));
|
||||
if (pid_column.has_value())
|
||||
row[*pid_column] = TRY(String::number(process.pid));
|
||||
if (ppid_column.has_value())
|
||||
row[*ppid_column] = TRY(String::number(process.ppid));
|
||||
if (pgid_column.has_value())
|
||||
row[*pgid_column] = TRY(String::number(process.pgid));
|
||||
if (sid_column.has_value())
|
||||
row[*sid_column] = TRY(String::number(process.sid));
|
||||
if (tty_column.has_value())
|
||||
row[*tty_column] = process.tty == "" ? "n/a"_short_string : TRY(String::from_deprecated_string(process.tty));
|
||||
if (state_column.has_value())
|
||||
row[*state_column] = process.threads.is_empty()
|
||||
? "Zombie"_short_string
|
||||
: TRY(String::from_deprecated_string(process.threads.first().state));
|
||||
if (cmd_column.has_value())
|
||||
row[*cmd_column] = TRY(String::from_deprecated_string(process.name));
|
||||
TRY(row.try_ensure_capacity(columns.size()));
|
||||
for (auto const& column : columns)
|
||||
row.unchecked_append(TRY(column_to_string(column.id, process)));
|
||||
|
||||
rows.unchecked_append(move(row));
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue