Processes & jobs
See every running process, move jobs between foreground and background, and kill a runaway script cleanly — or forcefully when you have no other choice.
What you'll learn
- What a process and a PID are, and how to find them with ps, pgrep, and top
- How to suspend, background, and foreground jobs with Ctrl+Z, bg, fg, and jobs
- The difference between SIGTERM and SIGKILL — and why kill -9 is a last resort
Before you start
The last lesson ended on a shift you may not have noticed: the moment you run a file, it stops being a file and becomes a living process — something that can hang, hog the machine, or need to outlive your terminal. This lesson is the control panel for those running things: how to see them, suspend them, background them, and — when one truly will not stop — kill it without wrecking your data.
What is a process?
Every program that runs on your computer is a process — a running instance of an executable, with its own memory, file handles, and state. The operating system tracks each one with a unique integer called a PID (process identifier). PIDs are assigned at birth and recycled after death; the same number may belong to different programs at different times.
You can have many processes from the same program simultaneously. Running python train.py twice creates two separate processes, each with its own PID.
Seeing what is running
ps aux — a snapshot
ps prints a snapshot of the current process table. The flags aux are nearly universal:
ps aux
USER PID %CPU %MEM VSZ RSS STAT STARTED TIME COMMAND
alice 412 0.0 0.1 33456 4096 Ss 09:01 0:00.03 /bin/zsh
alice 8821 98.2 4.7 3124000 386000 R 10:45 6:14.22 python train.py
alice 8900 0.0 0.0 33000 2048 S+ 10:46 0:00.01 ps aux
Key columns:
| Column | Meaning |
|---|---|
| PID | Process ID |
| %CPU | CPU share over the last second |
| %MEM | Fraction of physical RAM in use |
| STAT | State: R = running, S = sleeping, Z = zombie, T = stopped |
| COMMAND | The program and its arguments |
Filter with grep when you know what you are looking for:
ps aux | grep python
alice 8821 98.2 4.7 3124000 386000 R 10:45 6:14.22 python train.py
alice 8923 0.0 0.0 33000 1024 S+ 10:46 0:00.00 grep python
The second line is the grep command itself — a harmless artifact you can ignore.
top and htop — live views
top gives you a live, auto-refreshing view:
top
Press q to quit. The process consuming the most CPU floats to the top automatically.
If htop is installed (it usually is on Linux; brew install htop on macOS), prefer it — it adds color, mouse support, and easier sorting. Press F10 or q to exit.
Process states
A process is not just “running” or “not running.” The table below shows the states you will encounter:
| State | What it means | STAT code |
|---|---|---|
| Running | Actively using CPU right now | R |
| Sleeping | Waiting for I/O, a timer, or another process | S |
| Stopped | Suspended — frozen in place, not using CPU | T |
| Background | Running, but not attached to your terminal | S (bg) |
| Zombie | Finished, but parent has not yet collected exit code | Z |
The diagram below shows how a process moves between the states you control from the shell:
Foreground vs background
When you run a command normally, it runs in the foreground: it owns your terminal, you see its output, and you cannot type another command until it finishes.
To run a command in the background from the start, append &:
python train.py &
[1] 8821
The shell prints the job number (in brackets) and the PID. The job number is a shell-local shorthand; the PID is the system-wide identifier.
Moving a running process to the background
If you forgot & and the program is already running in the foreground:
- Press
Ctrl+Z— this sends SIGTSTP, which suspends (freezes) the process and gives you your prompt back. - Type
bgto resume it in the background.
bg
[1]+ python train.py &
Listing your jobs
jobs
[1]- Running python train.py &
[2]+ Stopped vim notes.txt
The + marks the most recent job; - marks the one before it.
Bringing a job back to the foreground
fg %1
%1 means job number 1. Just fg with no argument brings back the most recent job.
Stopping a process with Ctrl+C
While a process is in the foreground, Ctrl+C sends it SIGINT (signal number 2). Most programs treat this as a polite request to stop immediately. A well-written script catches SIGINT and exits cleanly — flushing buffers, closing files, removing lock files.
Sending signals with kill
Signals are messages the operating system delivers to processes. kill is the command that sends them — despite its name, it can send any signal, not just termination ones.
SIGTERM — the polite request
kill 8821
With no flag, kill sends SIGTERM (signal 15). The process receives the signal and is expected to clean up and exit gracefully. A training script that catches SIGTERM can save a checkpoint before shutting down.
SIGKILL — the forceful hammer
kill -9 8821
SIGKILL (signal 9) cannot be caught, blocked, or ignored. The kernel terminates the process immediately, with zero opportunity for cleanup.
Finding the right PID
If you do not know the PID, use pgrep to look it up by name:
pgrep -l python
8821 python
-l prints the name alongside the PID. To kill by name in one step, use pkill:
pkill python
This sends SIGTERM to every process whose name matches. Add -9 for SIGKILL, and -x to match the name exactly (avoiding collateral kills of unrelated processes).
Surviving logout with nohup
Background jobs (&) are still children of your shell. When you log out or close the terminal, the shell sends SIGHUP to all its children, which usually kills them.
nohup (“no hang-up”) makes a process immune to SIGHUP:
nohup python train.py > train.log 2>&1 &
[1] 9100
Output that would have gone to the terminal is redirected to train.log (or nohup.out if you omit the redirection). The process keeps running after you log out.
For long-running work, consider tmux or screen instead. They create a persistent terminal session you can detach from and reattach to later — even from a different machine:
tmux new -s training
python train.py
Press Ctrl+B then D to detach. The session (and your script) keeps running. Reattach later with tmux attach -t training.
Putting it all together
Here is a realistic workflow for a runaway training script:
ps aux | grep train.py
alice 8821 99.1 6.2 4200000 510000 R 10:45 42:11.00 python train.py
kill 8821
Wait a few seconds. If it is still there:
kill -9 8821
To run a new training job that survives logout and logs output:
nohup python train.py --epochs 100 > run.log 2>&1 &
echo "PID is $!"
$! is a special shell variable that expands to the PID of the last background command — handy to save so you can kill it later.
In one breath
Every running program is a process with a unique PID. See them with ps aux (a snapshot, pipe to grep) or top/htop (live). Control where they run: start in the background with a trailing &, or rescue a foreground job with Ctrl+Z (suspend) then bg, list them with jobs, pull one back with fg %1. To stop one, send a signal with kill: bare kill PID is SIGTERM (polite — the program can clean up and save state), Ctrl+C is SIGINT (also polite, foreground only), and kill -9 is SIGKILL — uncatchable, instant, and a last resort because it leaves corruption behind. Look up PIDs with pgrep/pkill, and use nohup ... & (or tmux) so a job survives logout.
Practice
Before the quiz, run the full lifecycle in your head. You launched python train.py in the foreground an hour ago; the machine is now crawling. List the exact steps to get your prompt back without killing it, push it to the background, confirm it is running there, and then — if it later hangs completely — stop it the right way first and the forceful way only if that fails. Which of those two kills risks a corrupt checkpoint, and why?
Quick check
A question to carry forward
You can now run, watch, and stop any process on the machine. But two invisible things have been quietly making all of it work, and you have never once questioned them. When you type git or python, the shell somehow knows which of the hundred thousand files on disk is the program you mean — and runs the right one instantly. And when a script needs your secret API key, or which database to talk to, it reads that from somewhere you never passed as an argument. Both answers live in the same place: the process’s environment — a set of named variables that every program silently inherits the moment it starts, chief among them the PATH that tells the shell where to find commands. Pull back the curtain on that environment and the last mysteries of the shell disappear. That is the final Power Tool, and it is the next lesson.