ps Command in Linux: Complete Guide to Process Status Reporting and Parameters

ps command in Linux and it perimeters

If top is the tool I use to watch a system live, ps is the tool I reach for when I need a precise, scriptable, exact snapshot of what’s running right now — perfect for piping into grep, awk, or a monitoring script. It’s one of the oldest commands in the Unix toolbox, and also one of the most confusingly inconsistent, because it supports three completely different option syntaxes at once (BSD, Unix, and GNU-long). I’ll untangle all of that here.

What is the ps Command?

ps stands for “process status.” It reports a static snapshot of currently running processes, pulling information from the /proc filesystem — the same source top and pstree use. Unlike top, it doesn’t refresh automatically; it prints once and exits, which is exactly what makes it so useful in shell scripts and one-off diagnostics.

Basic Syntax

ps supports three different styles of options, which is the source of most confusion:

ps [UNIX_OPTIONS]     # e.g. ps -ef
ps [BSD_OPTIONS]       # e.g. ps aux
ps [GNU_LONG_OPTIONS]  # e.g. ps --pid 1234

Yes, ps -ef and ps aux are both completely valid and both extremely common, but they come from different historical option conventions (System V/Unix vs. BSD) — that’s why one uses a leading dash and the other doesn’t.

The Two Most Common Invocations

ps aux (BSD-style)

ps aux

Real output from a test system:

USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root         1 10.5  0.1  15164  5508 ?        Sl   01:36   0:00 /process_api --firecracker-init --addr 0.0.0.0:2024
root         2  0.0  0.0      0     0 ?        S    01:36   0:00 [kthreadd]
root         3  0.0  0.0      0     0 ?        S    01:36   0:00 [pool_workqueue_release]
  • a — show processes for all users, not just the current one
  • u — display user-oriented format (owner, %CPU, %MEM, etc.)
  • x — include processes not attached to a controlling terminal (daemons, background services)

ps -ef (Unix-style)

ps -ef
  • -e — show every process
  • -f — full-format listing, including PPID and full command line

The two commands show largely overlapping information, formatted differently — aux emphasizes resource usage (%CPU, %MEM, memory sizes), while -ef emphasizes process relationships (PPID) and start time.

Understanding the Columns

ColumnMeaning
USEROwner of the process
PIDProcess ID
PPIDParent process ID (only shown with -f or similar full formats)
%CPUPercentage of CPU time used
%MEMPercentage of physical memory used
VSZVirtual memory size in KB
RSSResident Set Size — actual physical RAM used, in KB
TTYControlling terminal (or ? if none, common for daemons)
STATProcess state code (see below)
START / STIMEWhen the process started
TIMECumulative CPU time consumed
COMMAND / CMDThe command and arguments

Process State Codes

CodeMeaning
RRunning or runnable
SInterruptible sleep (waiting for an event)
DUninterruptible sleep (usually I/O — can’t be killed until it returns)
TStopped (by a job-control signal)
ZZombie — terminated, but not yet reaped by its parent
<High-priority process
NLow-priority (niced) process
sSession leader
lMulti-threaded
+Foreground process group

A process shown as Ss means it’s sleeping and is a session leader; R+ means it’s running in the foreground. These modifier letters stack onto the base state code.

Common Parameters

OptionDescription
-e / -AShow every process on the system
-fFull-format listing
-u <user>Show processes owned by a specific user
-p <pid>Show a specific process by PID
--forestDisplay an ASCII-art process tree, similar to pstree
-o <format>Customize exactly which columns to display
-C <name>Show processes matching a specific command name
-LShow threads for each process
-wwDon’t truncate the command column width
-o pid,ppid,cmdExample of custom-format output with selected fields only

Practical Examples

Sorted by memory usage, top consumers first

ps aux --sort=-%mem | head -6

Real example output:

USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root       503  4.0  0.8 2054228 35092 ?       Sl   01:37   0:00 /opt/rclone/rclone-filestore multimount
root         1  5.0  0.1  15168  5516 ?        Sl   01:36   0:00 /process_api --firecracker-init

This is one of my most-used one-liners — quickly identify the biggest memory consumers on a struggling system.

Sorted by CPU usage

ps aux --sort=-%cpu | head -10

Process tree view

ps -ef --forest

Real output showing kernel thread hierarchy:

UID        PID  PPID  C STIME TTY          TIME CMD
root         2     0  0 01:36 ?        00:00:00 [kthreadd]
root         3     2  0 01:36 ?        00:00:00  \_ [pool_workqueue_release]
root         4     2  0 01:36 ?        00:00:00  \_ [kworker/R-rcu_gp]

Custom column output for scripting

ps -eo pid,ppid,ni,pri,cmd | head -5

Real output:

  PID  PPID  NI PRI CMD
    1     0   0  19 /process_api --firecracker-init
    2     0   0  19 [kthreadd]
    3     2   0  19 [pool_workqueue_release]
    4     2 -20  39 [kworker/R-rcu_gp]

I use -o constantly when writing scripts — it produces predictable, parseable output without extra headers or oddly formatted columns.

Find a process by name

ps -C nginx -o pid,cmd

Find all processes owned by a specific user

ps -u www-data -f

Show only PIDs, for piping into another command

ps -C nginx -o pid=

The trailing = after a field name in -o suppresses the column header — extremely useful when you want to pipe raw PIDs directly into kill, renice, or similar.

kill $(ps -C nginx -o pid=)

How ps Works Internally

Like top and pstree, ps reads its data straight from /proc. For every numbered directory under /proc (each corresponding to a running process), it reads files like:

  • /proc/[pid]/stat — a single line of space-separated fields covering PID, state, PPID, priority, and more
  • /proc/[pid]/status — a more human-readable, multi-line version of similar data
  • /proc/[pid]/cmdline — the full command line, null-byte separated
  • /proc/[pid]/statm — memory usage statistics

Because ps doesn’t refresh continuously, there’s no “sampling window” the way top needs for CPU percentage calculation — %CPU in ps is typically computed based on cumulative CPU time divided by the process’s total elapsed run time, which can look different from top‘s more instantaneous CPU percentage.

Real-World Server Administration Examples

Finding zombie processes across the system

ps aux | awk '$8 ~ /^Z/ {print}'

Killing every process matching a pattern (careful!)

ps aux | grep '[n]ode' | awk '{print $2}' | xargs -r kill

The [n]ode bracket trick excludes the grep command itself from matching its own process listing — a small but genuinely useful habit.

Watching for processes in uninterruptible sleep (a sign of I/O trouble)

ps -eo pid,stat,cmd | awk '$2 ~ /^D/'

Checking how long a specific service has been running

ps -o etime= -p $(pgrep -o myapp)

etime shows elapsed time since the process started, which I use to confirm whether a service actually restarted after a deploy, or is still running the old process.

Troubleshooting Common Issues

Command column gets truncated — By default, ps truncates the CMD/COMMAND column to fit your terminal width. Use ww (double-w) to disable truncation entirely: ps auxww.

Confusing “no processes found” with an actual empty result — Double-check your filter isn’t accidentally excluding everything, e.g., ps -u nonexistentuser silently returns nothing rather than an error.

High RSS but process “isn’t doing anything” — RSS includes shared library memory mapped into the process; a large RSS on an otherwise idle process, especially something like a JVM or Python process, is often just its runtime and loaded libraries, not necessarily a leak. Compare against PSS (proportional set size) via tools like smem for a more accurate picture when memory is shared across many processes.

Performance Optimization Tips

  • Prefer ps -o with only the fields you need in scripts — it avoids unnecessary formatting overhead and produces easier-to-parse output than grepping fixed-width aux output.
  • For frequent polling in monitoring scripts, ps is lighter weight than repeatedly launching top -bn1, since it doesn’t need to compute the two-sample CPU delta top relies on.

Security Implications

ps aux and ps -ef reveal full command lines for every process on the system to any user by default on most distros — including any arguments passed on the command line. This is a well-known source of credential leakage: passing a password or API key as a bare CLI argument (mysql -u root -pMyPassword123) exposes it to every other user capable of running ps. Always prefer environment variables, config files with restricted permissions, or credential-helper mechanisms over CLI arguments for anything sensitive. Some hardened kernels and grsecurity-patched systems restrict ps visibility to a user’s own processes by default — worth knowing if output looks unexpectedly sparse on such a system.

ps vs. Related Commands

CommandDifference
topLive, auto-refreshing view, versus ps‘s single static snapshot
pstreeVisualizes the same underlying data as a hierarchy tree rather than a flat table
pgrep / pkillPurpose-built for finding/killing processes by name or attribute, often simpler than piping ps through grep
htopInteractive, colorized alternative combining much of ps and top‘s functionality

Compatibility Across Distributions

ps is available on every Linux distribution, provided by procps/procps-ng. The dual BSD/Unix/GNU option syntax (ps aux vs ps -ef vs ps --pid) is consistent across Debian, Ubuntu, RHEL, CentOS, Fedora, Arch, and openSUSE. Minimal/BusyBox-based systems (common in containers) provide a much more limited ps implementation, often only supporting a small subset of options — scripts relying on -o custom formatting or --sort may need adjustment on those systems.

Thread-Level Inspection with ps

For multi-threaded applications — Java services, database engines, anything built on a thread pool — inspecting individual threads rather than just the top-level process can be essential when diagnosing which specific thread is consuming CPU or stuck on I/O:

ps -L -p 1234

The -L flag shows a row per thread (LWP — lightweight process) rather than one row per process, adding an LWP column alongside the usual PID. Combined with custom output formatting:

ps -L -o pid,lwp,pcpu,stat,cmd -p 1234

This has been essential more than once when a Java application showed high aggregate CPU usage at the process level, but I needed to know exactly which thread (garbage collector? a specific worker pool thread?) was actually responsible, before handing off a thread dump for further analysis.

Combining ps With awk for Advanced Filtering

ps‘s column-based output pairs naturally with awk for filtering logic that goes beyond what ps‘s own options support directly:

# Find every process using more than 500MB of RSS
ps -eo pid,rss,cmd | awk '$2 > 512000 {print}'

# Sum total RSS memory used by all processes matching a name
ps -eo rss,cmd | grep '[n]ginx' | awk '{sum += $1} END {print sum/1024 " MB total"}'

That second one-liner is something I run regularly when trying to understand the true aggregate memory footprint of a multi-process application server (like Nginx or PHP-FPM, which run as a master process plus a pool of workers) rather than looking at any single worker process in isolation.

Historical Context: BSD vs. Unix vs. GNU Option Styles

The reason ps supports three overlapping, occasionally conflicting option syntaxes traces back to genuine historical divergence between Unix variants in the 1980s. BSD Unix and AT&T System V Unix each developed their own ps conventions independently, and when Linux’s procps package was built, it aimed for compatibility with both worlds simultaneously, plus adding its own GNU-style long options on top. This is why ps aux (no leading dash, BSD-style) and ps -ef (leading dash, Unix-style) both work and largely overlap in the information they show, while technically being entirely different option-parsing conventions under the hood. It’s also why mixing styles carelessly (e.g., ps -aux, with a dash before BSD-style options) can produce a warning or subtly different behavior depending on your specific procps-ng version — I always stick to one convention consistently within any given command rather than mixing them.

Watching Process Changes Over Time Without top

For situations where I want periodic snapshots logged to a file, rather than top‘s live interactive display, I loop ps with watch or a simple shell loop:

watch -n 5 'ps aux --sort=-%cpu | head -15'

Or, for logging to a file over time:

while true; do
    echo "=== $(date) ===" >> process-log.txt
    ps aux --sort=-%mem | head -10 >> process-log.txt
    sleep 60
done

This gives me a lightweight, appendable historical record of top resource consumers, useful for correlating a performance incident against exactly what was running and consuming resources at the time it happened, without the overhead of a full monitoring stack.

Summary

ps is the precise, scriptable counterpart to top‘s live dashboard — a single, reliable snapshot I can filter, sort, and pipe into other tools without worrying about a refreshing screen getting in the way. Learning the difference between aux and -ef, and getting comfortable with custom -o output, turns ps from a command you memorize one incantation of into a genuinely flexible tool for both interactive troubleshooting and serious automation.

References

  • Linux man-pages project, ps(1): https://man7.org/linux/man-pages/man1/ps.1.html
  • GNU/Linux procps-ng project: https://gitlab.com/procps-ng/procps
  • The Linux Kernel /proc filesystem documentation: https://www.kernel.org/doc/html/latest/filesystems/proc.html
Total
0
Shares

Leave a Reply

Previous Post
nice command in Linux and it perimeters

nice Command in Linux: Complete Guide to Process Priority Adjustment and Parameters

Next Post
printenv command in Linux and it perimeters

printenv Command in Linux: Complete Guide to Environment Variables Display and Parameters

Related Posts