Linux ps Command
last modified October 23, 2026
The ps (process status) command displays information about
running processes: their process IDs (PIDs), owners, CPU and memory usage,
states, and command lines. It takes a snapshot; for a continuously updated
view, use top or htop. ps is used to
find a process before sending it a signal,
to see what consumes resources, and to follow parent-child relationships.
The examples use ps from procps-ng 4.0 on Debian 12, in a
system booted with systemd that runs cron and nginx. The user
jano started a few background jobs in one terminal: a
sleep, a Python web server, a CPU-intensive script
(compute.py), a script that holds about 150 MB of memory
(cache.py), a script that leaves a zombie child
(zombie.py), a script with three threads
(workers.py), and a stopped sleep.
procps ps accepts three option styles: Unix options with a
dash (ps -ef), BSD options without a dash (ps
aux), and GNU long options (--sort). They can be mixed,
but ps -aux and ps aux are not the same command.
Processes of the current terminal
Without options, ps shows the processes that run in the
current terminal and belong to the current user.
ps
The columns are the PID, the terminal (pts/0), the cumulative
CPU time, and the program name. Only compute.py has used a
noticeable amount of CPU time. The list includes ps itself.
$ ps
PID TTY TIME CMD
60 pts/0 00:00:00 bash
71 pts/0 00:00:00 sleep
72 pts/0 00:00:00 python3
73 pts/0 00:00:06 python3
74 pts/0 00:00:00 python3
75 pts/0 00:00:00 python3
76 pts/0 00:00:00 python3
77 pts/0 00:00:00 sleep
79 pts/0 00:00:00 python3
86 pts/0 00:00:00 ps
All processes in BSD style
ps aux is the most common way to list every process with
resource usage.
ps aux
a selects the processes of all users that have a terminal,
x adds processes without a terminal, and u
selects the user-oriented format. %CPU is the CPU time divided
by the lifetime of the process, not the current load, so the busy
compute.py shows almost 100%. VSZ (virtual
memory) and RSS (resident memory) are in KiB.
STAT is the process state, explained later, and
START is the start time. User names longer than eight
characters are cut, as with message+ for
messagebus.
$ ps aux USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND root 1 0.6 0.0 19804 11568 ? Ss 13:34 0:00 /sbin/init root 21 0.0 0.0 32888 11472 ? Ss 13:34 0:00 /lib/systemd/systemd-journald root 29 0.0 0.0 7008 2508 ? Ss 13:34 0:00 /usr/sbin/cron -f message+ 30 0.0 0.0 7920 4212 ? Ss 13:34 0:00 /usr/bin/dbus-daemon --system --address=systemd: --nofork --nopidfile --systemd-activation --syslog-only root 33 0.0 0.0 16604 7844 ? Ss 13:34 0:00 /lib/systemd/systemd-logind root 43 0.0 0.0 15460 9632 ? Ss 13:34 0:00 sshd: /usr/sbin/sshd -D [listener] 0 of 10-100 startups root 57 0.0 0.0 10444 1576 ? Ss 13:34 0:00 nginx: master process /usr/sbin/nginx -g daemon on; master_process on; www-data 58 0.0 0.0 10788 2656 ? S 13:34 0:00 nginx: worker process www-data 59 0.0 0.0 10788 2644 ? S 13:34 0:00 nginx: worker process jano 60 0.1 0.0 7064 3612 pts/0 Ss+ 13:34 0:00 -bash jano 71 0.0 0.0 5472 1792 pts/0 S+ 13:34 0:00 sleep 3600 jano 72 4.4 0.1 38820 29680 pts/0 S+ 13:34 0:00 python3 -m http.server 8000 jano 73 99.8 0.0 16288 9544 pts/0 R+ 13:34 0:06 python3 compute.py jano 74 0.7 0.9 162776 156024 pts/0 S+ 13:34 0:00 python3 cache.py jano 75 0.1 0.0 16288 9528 pts/0 S+ 13:34 0:00 python3 zombie.py jano 76 0.4 0.0 239752 11912 pts/0 Sl+ 13:34 0:00 python3 workers.py jano 77 0.0 0.0 5472 1972 pts/0 T+ 13:34 0:00 sleep 900 jano 79 0.0 0.0 0 0 pts/0 Z+ 13:34 0:00 [python3] <defunct> jano 89 0.0 0.0 11092 4888 pts/0 R+ 13:34 0:00 ps aux
All processes in Unix style
The -e option selects every process and -f
produces a full-format listing.
ps -ef
The important addition is the PPID column, the PID of the
parent process. The nginx workers are children of the nginx master (57),
the zombie is a child of zombie.py, and the jobs are children
of the shell (60). That shell was started with docker exec, so
its parent is outside the container and shown as 0. C is the
CPU utilization and STIME the start time.
$ ps -ef UID PID PPID C STIME TTY TIME CMD root 1 0 0 13:34 ? 00:00:00 /sbin/init root 21 1 0 13:34 ? 00:00:00 /lib/systemd/systemd-journald root 29 1 0 13:34 ? 00:00:00 /usr/sbin/cron -f message+ 30 1 0 13:34 ? 00:00:00 /usr/bin/dbus-daemon --system --address=systemd: --nofork --nopidfile --systemd-activation --syslog-only root 33 1 0 13:34 ? 00:00:00 /lib/systemd/systemd-logind root 43 1 0 13:34 ? 00:00:00 sshd: /usr/sbin/sshd -D [listener] 0 of 10-100 startups root 57 1 0 13:34 ? 00:00:00 nginx: master process /usr/sbin/nginx -g daemon on; master_process on; www-data 58 57 0 13:34 ? 00:00:00 nginx: worker process www-data 59 57 0 13:34 ? 00:00:00 nginx: worker process jano 60 0 0 13:34 pts/0 00:00:00 -bash jano 71 60 0 13:34 pts/0 00:00:00 sleep 3600 jano 72 60 4 13:34 pts/0 00:00:00 python3 -m http.server 8000 jano 73 60 99 13:34 pts/0 00:00:06 python3 compute.py jano 74 60 0 13:34 pts/0 00:00:00 python3 cache.py jano 75 60 0 13:34 pts/0 00:00:00 python3 zombie.py jano 76 60 0 13:34 pts/0 00:00:00 python3 workers.py jano 77 60 0 13:34 pts/0 00:00:00 sleep 900 jano 79 75 0 13:34 pts/0 00:00:00 [python3] <defunct> jano 92 60 0 13:34 pts/0 00:00:00 ps -ef
Choosing the columns
The -o option selects the columns, and --sort
orders the rows.
ps -eo pid,ppid,user,%cpu,%mem,comm --sort=-%cpu | head -n 5
A minus sign before the sort key means descending order, so the processes
that use the most CPU come first. Choosing columns gives a compact,
predictable output for reports and scripts. ps L lists all
available column names.
$ ps -eo pid,ppid,user,%cpu,%mem,comm --sort=-%cpu | head -n 5
PID PPID USER %CPU %MEM COMMAND
73 60 jano 99.8 0.0 python3
72 60 jano 4.4 0.1 python3
74 60 jano 0.7 0.9 python3
1 0 root 0.6 0.0 systemd
Filtering by user
The -u option selects processes by effective user and
-U by real user.
ps -u www-data -o pid,user,comm ps -U root -o pid,comm | head -n 4
The two IDs differ for setuid programs: when a user runs
passwd, the real user is that user and the effective user is
root. Note that comm is limited to 15 characters, which is why
the journal daemon appears as systemd-journal.
$ ps -u www-data -o pid,user,comm
PID USER COMMAND
58 www-data nginx
59 www-data nginx
$ ps -U root -o pid,comm | head -n 4
PID COMMAND
1 systemd
21 systemd-journal
29 cron
Finding processes by name
The -C option selects processes by exact program name. The
pgrep command matches names with regular expressions and
prints PIDs.
ps -C nginx -o pid,ppid,user,comm pgrep nginx pgrep -a -u www-data nginx pgrep -af http.server
pgrep -a prints the full command line next to the PID, and
-u restricts the match to a user. By default,
pgrep matches the program name; -f matches the
whole command line, which is needed for scripts run by an interpreter such
as python3.
$ ps -C nginx -o pid,ppid,user,comm
PID PPID USER COMMAND
57 1 root nginx
58 57 www-data nginx
59 57 www-data nginx
$ pgrep nginx
57
58
59
$ pgrep -a -u www-data nginx
58 nginx: worker process
59 nginx: worker process
$ pgrep -af http.server
72 python3 -m http.server 8000
Inspecting a single process
The -p option selects processes by PID.
pid=$(pgrep -f cache.py) ps -p "$pid" -o pid,user,rss,etime,lstart,args ps -p 1 -o comm=
etime is the time since the process started, and
lstart the full start date. args shows the
command line with arguments, while comm shows the program
name; for PID 1, the command line is /sbin/init, a link to
systemd. An equals sign after a column name sets its header,
and an empty header removes the header line.
$ pid=$(pgrep -f cache.py)
$ ps -p "$pid" -o pid,user,rss,etime,lstart,args
PID USER RSS ELAPSED STARTED COMMAND
74 jano 156024 00:06 Fri Oct 23 13:34:24 2026 python3 cache.py
$ ps -p 1 -o comm=
systemd
Process tree
The --forest option draws the parent-child relationships.
ps -ef --forest
The tree shows at a glance which process started which. The zombie is
indented under zombie.py, and the nginx workers under their
master. The pstree command from the psmisc package draws a
more compact tree.
$ ps -ef --forest UID PID PPID C STIME TTY TIME CMD jano 60 0 0 13:34 pts/0 00:00:00 -bash jano 71 60 0 13:34 pts/0 00:00:00 \_ sleep 3600 jano 72 60 4 13:34 pts/0 00:00:00 \_ python3 -m http.server 8000 jano 73 60 99 13:34 pts/0 00:00:06 \_ python3 compute.py jano 74 60 0 13:34 pts/0 00:00:00 \_ python3 cache.py jano 75 60 0 13:34 pts/0 00:00:00 \_ python3 zombie.py jano 79 75 0 13:34 pts/0 00:00:00 | \_ [python3] <defunct> jano 76 60 0 13:34 pts/0 00:00:00 \_ python3 workers.py jano 77 60 0 13:34 pts/0 00:00:00 \_ sleep 900 jano 121 60 0 13:34 pts/0 00:00:00 \_ ps -ef --forest root 1 0 0 13:34 ? 00:00:00 /sbin/init root 21 1 0 13:34 ? 00:00:00 /lib/systemd/systemd-journald root 29 1 0 13:34 ? 00:00:00 /usr/sbin/cron -f message+ 30 1 0 13:34 ? 00:00:00 /usr/bin/dbus-daemon --system --address=systemd: --nofork --nopidfile --systemd-activation --syslog-only root 33 1 0 13:34 ? 00:00:00 /lib/systemd/systemd-logind root 43 1 0 13:34 ? 00:00:00 sshd: /usr/sbin/sshd -D [listener] 0 of 10-100 startups root 57 1 0 13:34 ? 00:00:00 nginx: master process /usr/sbin/nginx -g daemon on; master_process on; www-data 58 57 0 13:34 ? 00:00:00 \_ nginx: worker process www-data 59 57 0 13:34 ? 00:00:00 \_ nginx: worker process
Sorting by memory
To find the processes that use the most memory, sort by
rss.
ps -eo pid,rss,vsz,%mem,comm --sort=-rss | head -n 4
RSS (resident set size) is the physical memory a process uses now. VSZ is
its virtual address space, which includes memory that was reserved but
never used; workers.py has a large VSZ because each thread
reserves a stack, but a small RSS. On this 16 GB machine,
cache.py uses 0.9% of the memory.
$ ps -eo pid,rss,vsz,%mem,comm --sort=-rss | head -n 4
PID RSS VSZ %MEM COMMAND
74 156024 162776 0.9 python3
72 29680 38820 0.1 python3
76 11912 239752 0.0 python3
Process states
The STAT column shows the state of each process, followed by
optional flags.
ps -o pid,stat,comm,args
Without -e, ps -o shows the processes of the
current terminal. The states are R (running or runnable),
S (sleeping, waiting for an event), D
(uninterruptible sleep, usually disk I/O), T (stopped, for
example with Ctrl+Z or SIGSTOP), and
Z (zombie). A zombie has finished, but its parent has not
collected its exit status; it uses no memory and disappears when the parent
calls wait or exits. The flags are s (session
leader), l (multithreaded), and + (in the
foreground process group of the terminal).
$ ps -o pid,stat,comm,args
PID STAT COMMAND COMMAND
60 Ss+ bash -bash
71 S+ sleep sleep 3600
72 S+ python3 python3 -m http.server 8000
73 R+ python3 python3 compute.py
74 S+ python3 python3 cache.py
75 S+ python3 python3 zombie.py
76 Sl+ python3 python3 workers.py
77 T+ sleep sleep 900
79 Z+ python3 [python3] <defunct>
128 R+ ps ps -o pid,stat,comm,args
Threads
The nlwp column shows the number of threads, and the
-L option lists each thread on its own line.
ps -o pid,nlwp,comm -p $(pgrep -f workers.py) ps -L -o pid,lwp,stat,comm -p $(pgrep -f workers.py)
workers.py has four threads: the main thread and three
workers. The LWP column is the thread ID; the main thread's
ID equals the PID. Python 3.14 names its threads, and the names, cut to 15
characters, appear in the comm column.
$ ps -o pid,nlwp,comm -p $(pgrep -f workers.py)
PID NLWP COMMAND
76 4 python3
$ ps -L -o pid,lwp,stat,comm -p $(pgrep -f workers.py)
PID LWP STAT COMMAND
76 76 Sl+ python3
76 80 Sl+ Thread-1 (sleep
76 81 Sl+ Thread-2 (sleep
76 82 Sl+ Thread-3 (sleep
ps with grep
Piping ps aux to grep is a common way to find a
process, with one catch.
ps aux | grep http.server ps aux | grep '[h]ttp.server'
The grep process itself contains the pattern in its command
line and appears in the result. The bracket trick avoids that: the pattern
[h]ttp.server still matches http.server, but the
command line of grep now contains [h]ttp, which
does not match. pgrep -af is simpler and never matches
itself.
$ ps aux | grep http.server jano 72 4.3 0.1 38820 29680 pts/0 S+ 13:34 0:00 python3 -m http.server 8000 jano 139 0.0 0.0 6472 2280 pts/0 S+ 13:34 0:00 grep http.server $ ps aux | grep '[h]ttp.server' jano 72 4.3 0.1 38820 29680 pts/0 S+ 13:34 0:00 python3 -m http.server 8000
ps in scripts
For scripts, remove the header and choose exactly the columns you need.
ps -o pid= -C nginx pid=$(pgrep -f http.server) if ps -p "$pid" > /dev/null; then echo "server $pid is running"; fi ps -o pid,user=OWNER,etimes=SECONDS,comm=PROGRAM -C nginx
ps -p exits with status 0 when the process exists, so it can
be used directly in a condition. etimes gives the elapsed time
in seconds, which is easier to compare than the
[[dd-]hh:]mm:ss format of etime. Custom headers
make reports easier to read.
$ ps -o pid= -C nginx
57
58
59
$ pid=$(pgrep -f http.server)
$ if ps -p "$pid" > /dev/null; then echo "server $pid is running"; fi
server 72 is running
$ ps -o pid,user=OWNER,etimes=SECONDS,comm=PROGRAM -C nginx
PID OWNER SECONDS PROGRAM
57 root 8 nginx
58 www-data 8 nginx
59 www-data 8 nginx
Best practices for ps
- Use ps -eo for scripts: Choose columns and use
=to remove headers instead of parsingps auxwithawk. - Use pgrep for PIDs: It matches by pattern, user, or full command line and does not match itself.
- Read %CPU carefully: It is an average over the
lifetime of the process; use
topfor current usage. - Look at RSS, not VSZ: RSS is the memory actually used.
- Follow the tree: Use
--forestorPPIDto find which parent starts unwanted processes or leaves zombies.
Source
In this article, we have used the ps command to list
processes, choose and sort columns, filter by user, name, and PID, display
process trees, states, and threads, and use ps in scripts.
Author
List all Linux tutorials.