ZetCode

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

Source

ps(1) manual page

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

My name is Jan Bodnar, and I am a passionate programmer with extensive programming experience. I have been writing programming articles since 2007. To date, I have authored over 1,400 articles and 8 e-books. I possess more than ten years of experience in teaching programming.

List all Linux tutorials.