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
| Column | Meaning |
|---|---|
USER | Owner of the process |
PID | Process ID |
PPID | Parent process ID (only shown with -f or similar full formats) |
%CPU | Percentage of CPU time used |
%MEM | Percentage of physical memory used |
VSZ | Virtual memory size in KB |
RSS | Resident Set Size — actual physical RAM used, in KB |
TTY | Controlling terminal (or ? if none, common for daemons) |
STAT | Process state code (see below) |
START / STIME | When the process started |
TIME | Cumulative CPU time consumed |
COMMAND / CMD | The command and arguments |
Process State Codes
| Code | Meaning |
|---|---|
R | Running or runnable |
S | Interruptible sleep (waiting for an event) |
D | Uninterruptible sleep (usually I/O — can’t be killed until it returns) |
T | Stopped (by a job-control signal) |
Z | Zombie — terminated, but not yet reaped by its parent |
< | High-priority process |
N | Low-priority (niced) process |
s | Session leader |
l | Multi-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
| Option | Description |
|---|---|
-e / -A | Show every process on the system |
-f | Full-format listing |
-u <user> | Show processes owned by a specific user |
-p <pid> | Show a specific process by PID |
--forest | Display 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 |
-L | Show threads for each process |
-ww | Don’t truncate the command column width |
-o pid,ppid,cmd | Example 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 -owith only the fields you need in scripts — it avoids unnecessary formatting overhead and produces easier-to-parse output than grepping fixed-widthauxoutput. - For frequent polling in monitoring scripts,
psis lighter weight than repeatedly launchingtop -bn1, since it doesn’t need to compute the two-sample CPU deltatoprelies 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
| Command | Difference |
|---|---|
top | Live, auto-refreshing view, versus ps‘s single static snapshot |
pstree | Visualizes the same underlying data as a hierarchy tree rather than a flat table |
pgrep / pkill | Purpose-built for finding/killing processes by name or attribute, often simpler than piping ps through grep |
htop | Interactive, 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-ngproject: https://gitlab.com/procps-ng/procps - The Linux Kernel
/procfilesystem documentation: https://www.kernel.org/doc/html/latest/filesystems/proc.html