MA-CLI1 · Module 1.11
Processes and jobs
Topics: ps · jobs · kill
Overview
Module 1.11 — Processes and jobs
Every command you run becomes a process: a running program with its own ID number. Most commands finish in a blink, but some, like a long tidewatch sync, keep going for minutes. This module shows you how to see what is running, how to push work into the background, and how to stop a program politely before reaching for anything forceful.
By the end you should be able to:
- explain what a process and a PID are;
- read a light
psorps auxlisting; - run a command in the background with
&and manage it withjobs,fg, andbg; - tell Ctrl-C from Ctrl-Z;
- stop a program with
kill, and treatkill -9as the last resort.
Scroll Core ideas → worked examples → Quiz. Plan on roughly 45–55 minutes.
Concepts
Core ideas — programs that are running right now
Up to now every command Sam has typed finished almost instantly. ls prints and returns, cp copies and returns. You type, you wait a fraction of a second, you get the prompt back. That rhythm hides an important fact: each of those commands was a running program for a brief moment, and the computer was keeping track of it.
This module is about the moments when a program does not finish quickly. Imagine the tidewatch project has a script that downloads the latest gauge readings from a remote station and merges them into data/raw/. It might take two minutes, or ten. While it works, your terminal sits there with no prompt. What are your options? Can you do something else in the meantime? Can you stop it if it looks wrong? The answers all start with understanding processes.
What is a process?
A program is a file on disk: a set of instructions waiting to be used. A process is what exists when you actually run that program. The same program can be running several times at once, and each run is a separate process with its own memory and its own state.
A helpful comparison is a recipe and a cook. The recipe card sits in a drawer (the program). When someone follows it in the kitchen, there is an activity going on (the process). Two cooks can follow the same card at the same moment, and they will not interfere with each other.
Every process receives a PID, short for process ID. A PID is a whole number the system hands out to keep processes distinct, like a ticket number at a counter. It is the name you use when you need to talk about one particular running program, for example to stop it.
Processes also have a family tree. When you type a command in the shell, the shell starts a child process to run it. The shell is the parent. This is the same parent-and-child idea you met with exported variables in module 1.10: the child inherits a copy of the exported environment.
Looking at processes — ps
The command ps (process status) prints a snapshot of running processes. Run on its own, it shows only the ones attached to your current terminal:
ps
PID TTY TIME CMD
4102 ttys001 0:00.04 -zsh
4377 ttys001 0:00.00 ps
Here is how to read it:
| Column | Meaning |
|--------|---------|
| PID | the process ID |
| TTY | which terminal the process is attached to |
| TIME | how much processor time it has used (not how long it has existed) |
| CMD | the command that was started |
Notice that two processes appear: your shell (-zsh, or bash for bash users) and the ps command itself, which was running at the moment of the snapshot. Seeing ps list itself is normal, and a good reminder that the listing is a photograph of one instant.
To see everything on the machine, add options. Two common spellings do almost the same job:
ps aux
ps -ef
ps aux gives one line per process, with these columns you will meet most often:
USER PID %CPU %MEM STAT START TIME COMMAND
sam 4102 0.0 0.1 Ss 09:12 0:00.04 -zsh
sam 4890 0.4 0.2 S 09:30 0:01.12 /bin/sh ./scripts/slow_sync.sh
root 1 0.0 0.0 Ss Oct12 0:03.51 /sbin/launchd
You do not have to understand every column. For beginners, four are enough: who owns it (USER), which one it is (PID), how busy it is (%CPU), and what it is (COMMAND). The ps -ef form shows a similar table but adds a PPID column, the PID of the parent, which helps when you want to see who started whom.
The full list can run to hundreds of lines. A common trick is to pass the output through a pipe | to grep, which keeps only lines containing a word you choose:
ps aux | grep slow_sync
The pipe takes what ps aux prints and feeds it to grep instead of the screen. You will see your target line, and usually a second line for the grep itself, since it too contains the word you were searching for. Pipes get much more attention later; for now, treat this as a ready-made phrase for "find my program."
The foreground: one thing at a time
When you run a command normally, it runs in the foreground. The shell waits for it to finish before giving you another prompt. While a foreground program is active, your keystrokes go to it, not to the shell. That is why a long-running command seems to "freeze" the terminal: nothing is broken, the shell is simply waiting its turn.
Suppose Sam has a script scripts/slow_sync.sh that pretends to pull readings from a remote station:
#!/bin/sh
echo "sync started"
sleep 120
echo "sync finished"
(sleep 120 just waits for 120 seconds, standing in for real download time.) Run it normally and the terminal is occupied for two minutes:
./scripts/slow_sync.sh
sync started
Nothing else will happen until the wait ends. You have two broad choices: stop it, or let it run somewhere out of the way.
Stopping the foreground program — Ctrl-C
Ctrl-C (hold Control, press C) asks the foreground program to interrupt itself. In the vast majority of cases, the program ends and you get your prompt back:
sync started
^C
The ^C is the terminal echoing your keypress. Use Ctrl-C when you started something by mistake, when it is taking too long, or when it is one of those programs that never ends by itself, such as tail -f from module 1.6.
It is not a copy command. In many graphical programs Ctrl-C means "copy," but in the terminal it means "interrupt the running program." That difference trips up many newcomers.
Pausing the foreground program — Ctrl-Z
Ctrl-Z does something gentler: it suspends the foreground program. The process is frozen, not ended. It keeps its place and its memory but does no work, and the shell gives you the prompt back.
sync started
^Z
[1]+ Stopped ./scripts/slow_sync.sh
The [1] is the job number, a small shell-managed number we will use below. The word Stopped tells you the process is paused. It still exists, and it can continue later. Ctrl-Z by itself leaves a frozen process behind, so do not forget it exists; the next sections explain how to resume or end it.
Memory aid: C for Cancel, Z for "pause so I can come back" (a nap, not an ending).
Running in the background — &
Instead of waiting, you can launch a command straight into the background by adding an ampersand to the end of the line:
./scripts/slow_sync.sh &
[1] 4890
Two numbers appear: [1] is the job number and 4890 is the PID. The prompt returns immediately, and the script keeps working. You can type other commands while it runs.
A background program can still print to your terminal. When the script reaches its echo "sync finished", that line will appear wherever the cursor happens to be, possibly in the middle of something you are typing. It looks messy, but it is harmless; press Enter to get a fresh prompt.
Backgrounding fits best when a program does not need your input and does not produce output you must watch.
Jobs: the shell's to-do list — jobs, fg, bg
The shell keeps a small list of the commands you started from this terminal that are either running in the background or stopped. These are jobs, and the word is easy to mix up with processes. The difference: a process is the system-wide idea with a PID; a job is the shell's own bookkeeping for commands launched from this terminal, with a small number like [1].
jobs
[1]+ Running ./scripts/slow_sync.sh &
A job can be in one of two states you will see most often: Running (working in the background) and Stopped (frozen). Three commands move jobs between states:
| Command | Effect |
|---------|--------|
| fg | bring a job to the foreground (it takes over the terminal again) |
| bg | let a stopped job continue running in the background |
| jobs | list the jobs and their states |
When there is more than one job, name which by number with a percent sign: fg %2, bg %1. With no argument, these commands act on the current job, the one marked with a + in the jobs list.
A typical combination is the "pause and park" pattern:
- Start a long command normally.
- Press Ctrl-Z to freeze it.
- Type
bgso it resumes in the background. - Use the prompt for other things, and
fglater if you want to watch it again.
Common confusion. Ctrl-Z alone does not keep the work going; the program is paused. People sometimes press it expecting the sync to continue and then wonder why nothing finishes. You need
bg(orfg) to wake it up.
One more habit worth knowing: background jobs belong to the terminal they were started from. In many setups, closing that terminal window ends them. For a job that must survive, there are tools such as nohup and terminal multiplexers, which belong to a later stage of your learning.
Ending a process from outside — kill
Sometimes you cannot press Ctrl-C: the program is in the background, or in another terminal window, or something has stopped responding. The kill command lets you send a message, called a signal, to a process by PID.
Despite its dramatic name, kill by default is polite. It sends the TERM (terminate) signal, which asks the program to shut down tidily: finish writing its files, clean up, and exit.
kill 4890
If the process is a job in your current shell, you can use the job number with a percent sign instead of looking up the PID:
kill %1
After a moment, jobs will report the job as Terminated. If the process had a chance to close cleanly, it did. That is why the polite signal is almost always the right first choice.
When politeness does not work — kill -9
Occasionally a program ignores the polite request, because it is stuck or waiting on something that will never arrive. For those cases there is a stronger option:
kill -9 4890
Signal 9 (called KILL) cannot be ignored. The system removes the process immediately, without giving it any chance to tidy up. That makes it effective, but also risky: a program that was halfway through writing data/raw/gauge_a.csv could leave a half-written file behind.
So follow this order, always:
- Try Ctrl-C if the program is in the foreground.
- Try plain
kill PIDand wait a few seconds. Check withpsorjobs. - Only if it is still there, use
kill -9 PID.
Two safety habits go with this. First, double-check the PID before pressing Enter: use ps aux | grep name and read the line carefully, because ending the wrong process is easy and sometimes costly. Second, never use kill -9 as a first move just because it feels decisive.
A quick map of the keys and commands
| You want to… | Use |
|--------------|-----|
| see what is running in this terminal | ps |
| see everything, with owners | ps aux (or ps -ef) |
| run a command without waiting | add & to the line |
| list your shell's jobs | jobs |
| cancel the foreground program | Ctrl-C |
| pause the foreground program | Ctrl-Z |
| resume a paused job in the background | bg |
| bring a job back to the front | fg |
| ask a program to quit politely | kill PID or kill %N |
| force a stuck program to end (last resort) | kill -9 PID |
Summary
- A process is a running program; the system labels each one with a PID.
psshows a snapshot;ps auxandps -efshow the whole machine. Focus on owner, PID, and command.- Foreground programs hold the terminal;
&sends a command to the background. - Jobs are the shell's numbered list of background and stopped commands:
jobs,fg,bg. - Ctrl-C interrupts; Ctrl-Z only pauses, and
bgorfgresumes. kill PIDis the polite request;kill -9is the last resort because the program gets no chance to clean up.
Examples
Worked examples — a long tidewatch sync
Sam has a script that pretends to pull fresh readings from a remote station. It takes a couple of minutes, which is exactly the situation where processes and jobs matter. Each scene gives a situation, the commands, an interpretation, and a takeaway. PIDs and times on your machine will differ; follow the pattern.
First, a small setup:
cd ~/cli-practice/tidewatch
mkdir -p scripts
printf '#!/bin/sh\necho "sync started"\nsleep 120\necho "sync finished"\n' > scripts/slow_sync.sh
chmod u+x scripts/slow_sync.sh
Scene 1 — The terminal is occupied
Scene. Sam runs the script the ordinary way and watches nothing happen.
./scripts/slow_sync.sh
sync started
There is no prompt, and typing does nothing useful, because this is a foreground process and the shell is waiting for it. Sam decides he did not mean to start it yet and presses Ctrl-C:
sync started
^C
The prompt returns at once.
Interpretation. Ctrl-C interrupted the foreground program, and the process ended. Nothing was left running, and no files were harmed because the script only waits.
Takeaway. A silent terminal usually means a foreground program is working, not that the shell is broken. Ctrl-C is the standard way out.
Scene 2 — Starting it in the background
Scene. This time Sam wants to keep working, so he adds &.
./scripts/slow_sync.sh &
jobs
ps
[1] 4890
sync started
[1]+ Running ./scripts/slow_sync.sh &
PID TTY TIME CMD
4102 ttys001 0:00.05 -zsh
4890 ttys001 0:00.00 /bin/sh ./scripts/slow_sync.sh
4902 ttys001 0:00.00 sleep 120
4915 ttys001 0:00.00 ps
Interpretation. The shell printed [1] 4890 straight away: job number 1, PID 4890. The prompt came back immediately, so Sam could type jobs and ps. jobs shows the job as Running. ps reveals something interesting: there are two processes for the script. One is the shell running the script (4890), and the other is sleep 120 (4902), a child that the script started. Programs often start other programs, and the family tree is visible here.
Takeaway. & gives you the prompt back while the work continues. jobs is the shell's view with a job number; ps is the system's view with PIDs.
Scene 3 — Pause, park, and resume
Scene. Sam starts the sync in the foreground, forgetting the &. Rather than cancel, he wants to move it into the background without losing progress.
./scripts/slow_sync.sh
sync started
^Z
[1]+ Stopped ./scripts/slow_sync.sh
Pressing Ctrl-Z froze the process and returned the prompt. The state is Stopped, so no work is happening. Sam wakes it with bg and checks:
bg
jobs
[1]+ ./scripts/slow_sync.sh &
[1]+ Running ./scripts/slow_sync.sh &
Later, Sam wants to watch it finish, so he brings it to the front:
fg
./scripts/slow_sync.sh
sync finished
Interpretation. The same process moved through three states: running in the foreground, stopped, and running in the background, then back to the foreground. Its PID never changed because it was never restarted. When the script reached its end, it printed sync finished and the job ended.
Takeaway. The "pause and park" recipe is Ctrl-Z, then bg. Ctrl-Z alone only pauses; without bg or fg the process stays frozen.
Scene 4 — Stopping a background job politely
Scene. A background sync is running, and Sam realises he pointed it at the wrong station. He wants it gone, but gently. First he finds the PID, to be sure he has the right one:
./scripts/slow_sync.sh &
ps aux | grep slow_sync
[1] 5120
sam 5120 0.0 0.0 /bin/sh ./scripts/slow_sync.sh
sam 5141 0.0 0.0 grep slow_sync
The first line is the real target. The second is the grep command matching its own search text, which can be ignored. Sam sends the polite signal, by PID, and then checks:
kill 5120
jobs
[1]+ Terminated ./scripts/slow_sync.sh
Interpretation. kill 5120 sent the TERM signal, a request to shut down. The script stopped, and jobs reports it as Terminated. Sam could equally have typed kill %1, using the job number, since the process belongs to this shell.
Takeaway. Read the ps line carefully before pressing Enter, and always start with plain kill. It is enough in nearly every case.
Scene 5 — A stubborn process and the last resort
Scene. Imagine a different helper that traps the polite signal and carries on regardless. Sam sends kill, waits, and checks again:
kill 5300
ps aux | grep stubborn_sync
sam 5300 0.0 0.0 /bin/sh ./scripts/stubborn_sync.sh
sam 5388 0.0 0.0 grep stubborn_sync
The process is still there after a few seconds. The polite request failed, so Sam escalates once, after confirming that the PID is correct:
kill -9 5300
ps aux | grep stubborn_sync
sam 5402 0.0 0.0 grep stubborn_sync
Interpretation. Signal 9 cannot be ignored, so the process vanished immediately. The only remaining line is the grep itself. The price is that the script had no chance to clean up. If it had been in the middle of writing a file, Sam should now inspect that file with ls -l and tail, and be ready to re-run the step, because it may be incomplete.
Takeaway. The order is Ctrl-C, then kill, then kill -9, and each step is chosen only after the previous one has failed. After a forced end, check any files the program was writing.
Practice check — processes and jobs
Multiple-choice check from the Matheion app lesson. Pick an answer for each question, then submit.
1.What is a PID?
2.Sam types `./scripts/slow_sync.sh &`. What does the trailing `&` do?
3.A sync is running in the foreground and Sam wants to stop it for good. Which key combination should he try first?
4.Sam pressed Ctrl-Z during a sync and `jobs` shows `Stopped`. What single command lets the sync continue working in the background?
5.`ps aux` prints hundreds of lines. Sam wants to find the line for his sync script. Which is the most sensible approach?
6.Why is `kill PID` preferred over `kill -9 PID` as a first attempt?
7.A background sync was ended with `kill -9`. What is a sensible follow-up?