mirror of
https://github.com/SerenityOS/serenity.git
synced 2025-01-23 01:41:59 -05: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
2 changed files with 178 additions and 65 deletions
|
@ -5,7 +5,7 @@ ps - list currently running processes
|
||||||
## Synopsis
|
## Synopsis
|
||||||
|
|
||||||
```**sh
|
```**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
|
## 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`: Consider all processes that are associated with a TTY.
|
||||||
* `-A` or `-e`: Consider all processes, not just those in the current 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.)
|
* `-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.
|
* `-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.
|
* `--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.
|
* `-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
|
## Examples
|
||||||
|
|
||||||
|
Show all processes (full format):
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
$ ps -ef
|
$ 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
|
## See Also
|
||||||
* [`pmap`(1)](help://man/1/pmap)
|
* [`pmap`(1)](help://man/1/pmap)
|
||||||
* [`lsof`(1)](help://man/1/lsof)
|
* [`lsof`(1)](help://man/1/lsof)
|
||||||
|
|
|
@ -16,6 +16,113 @@
|
||||||
#include <sys/sysmacros.h>
|
#include <sys/sysmacros.h>
|
||||||
#include <unistd.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)
|
static ErrorOr<Optional<String>> tty_stat_to_pseudo_name(struct stat tty_stat)
|
||||||
{
|
{
|
||||||
int tty_device_major = major(tty_stat.st_rdev);
|
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("/dev/", "r"));
|
||||||
TRY(Core::System::unveil(nullptr, nullptr));
|
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_process_flag = false;
|
||||||
bool every_terminal_process_flag = false;
|
bool every_terminal_process_flag = false;
|
||||||
bool full_format_flag = false;
|
bool full_format_flag = false;
|
||||||
bool provided_filtering_option = false;
|
bool provided_filtering_option = false;
|
||||||
bool provided_quick_pid_list = false;
|
bool provided_quick_pid_list = false;
|
||||||
|
Vector<Column> columns;
|
||||||
Vector<pid_t> pid_list;
|
Vector<pid_t> pid_list;
|
||||||
Vector<pid_t> parent_pid_list;
|
Vector<pid_t> parent_pid_list;
|
||||||
Vector<DeprecatedString> tty_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", nullptr, 'A');
|
||||||
args_parser.add_option(every_process_flag, "Show every process (Equivalent to -A)", nullptr, 'e');
|
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(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) {
|
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;
|
provided_filtering_option = true;
|
||||||
auto pid = pid_string.to_int();
|
auto pid = pid_string.to_int();
|
||||||
|
@ -180,37 +284,29 @@ ErrorOr<int> serenity_main(Main::Arguments arguments)
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
Vector<Column> columns;
|
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 {};
|
||||||
|
};
|
||||||
|
|
||||||
Optional<size_t> uid_column;
|
if (full_format_flag) {
|
||||||
Optional<size_t> pid_column;
|
TRY(columns.try_ensure_capacity(8));
|
||||||
Optional<size_t> ppid_column;
|
TRY(add_default_column(ColumnId::UserId));
|
||||||
Optional<size_t> pgid_column;
|
TRY(add_default_column(ColumnId::ProcessId));
|
||||||
Optional<size_t> sid_column;
|
TRY(add_default_column(ColumnId::ParentProcessId));
|
||||||
Optional<size_t> state_column;
|
TRY(add_default_column(ColumnId::ProcessGroupId));
|
||||||
Optional<size_t> tty_column;
|
TRY(add_default_column(ColumnId::SessionId));
|
||||||
Optional<size_t> cmd_column;
|
TRY(add_default_column(ColumnId::State));
|
||||||
|
TRY(add_default_column(ColumnId::TTY));
|
||||||
auto add_column = [&](auto title, auto alignment) {
|
TRY(add_default_column(ColumnId::Command));
|
||||||
columns.unchecked_append({ title, alignment, 0, {} });
|
} else {
|
||||||
return columns.size() - 1;
|
TRY(columns.try_ensure_capacity(3));
|
||||||
};
|
TRY(add_default_column(ColumnId::ProcessId));
|
||||||
|
TRY(add_default_column(ColumnId::TTY));
|
||||||
if (full_format_flag) {
|
TRY(add_default_column(ColumnId::Command));
|
||||||
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);
|
|
||||||
} 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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
auto all_processes = TRY(Core::ProcessStatisticsReader::get_all());
|
auto all_processes = TRY(Core::ProcessStatisticsReader::get_all());
|
||||||
|
@ -252,32 +348,21 @@ ErrorOr<int> serenity_main(Main::Arguments arguments)
|
||||||
|
|
||||||
Vector<String> header;
|
Vector<String> header;
|
||||||
TRY(header.try_ensure_capacity(columns.size()));
|
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);
|
header.unchecked_append(column.title);
|
||||||
rows.unchecked_append(move(header));
|
}
|
||||||
|
|
||||||
|
if (!header_is_empty)
|
||||||
|
rows.unchecked_append(move(header));
|
||||||
|
|
||||||
for (auto const& process : processes) {
|
for (auto const& process : processes) {
|
||||||
Vector<String> row;
|
Vector<String> row;
|
||||||
TRY(row.try_resize(columns.size()));
|
TRY(row.try_ensure_capacity(columns.size()));
|
||||||
|
for (auto const& column : columns)
|
||||||
if (uid_column.has_value())
|
row.unchecked_append(TRY(column_to_string(column.id, process)));
|
||||||
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));
|
|
||||||
|
|
||||||
rows.unchecked_append(move(row));
|
rows.unchecked_append(move(row));
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue