# Stopping Things: kill and Signals

kill sends a process a signal: plain kill sends TERM, a polite request to finish up and stop; kill -KILL ends a process that ignores it; pgrep -af and pkill -f find and stop processes by name; and Ctrl+C sends INT to the program in front of you.

Journeyman · Unix · Linux · Updated November 10, 2026

## After this lesson you can

- Stop a process by its PID with kill, and send -KILL only when the polite -TERM doesn't work
- Name the signal that Ctrl+C, kill and kill -KILL send, and what a program can do with each
- Find and stop processes by name with pgrep -af and pkill -f, checking with pgrep first

## Key takeaways

- kill PID sends the process TERM, a signal that means: please finish up and stop. Most programs stop at once.
- A program can catch TERM to save its work, or ignore it. kill -KILL (or kill -9) can't be caught: the process ends on the spot, with no chance to clean up.
- Send TERM first, check with ps -p PID, and send KILL only when the process is still there.
- pgrep -af NAME lists matching processes and pkill -f NAME stops them. Look with pgrep before you stop with pkill.
- Ctrl+C sends INT, the interrupt signal, to the program running in front of you in that terminal.

A program has frozen, and its window won't close. A script you started an hour ago is stuck in a loop, draining the battery. On a server, a copy of a website that crashed halfway still holds on to its address, so the new copy can't start. Ctrl+C stops only the program running in front of you, in your own terminal. For everything else there's `kill`: the terminal's Force Quit, which administrators use every week and developers reach for whenever a test or a server won't stop.

These examples use the practice folder and the two terminals from *What's Running: ps, top and htop*. Add one more program, `stubborn.sh`, which refuses to stop when it's asked nicely:

```bash
cd ~/terminal-practice/processes
printf '%s\n' '#!/usr/bin/env bash' '# stubborn.sh: ignores a polite request to stop.' "trap 'echo \"Not now, I am busy.\"' TERM" 'while true; do sleep 1; done' > stubborn.sh
chmod +x stubborn.sh
```

## kill sends a signal

Asking someone to leave a party can go two ways. You can tap them on the shoulder and say it's time to go, so they find their coat and say goodbye; or security can carry them out. Processes get the same two choices through **signals**: short messages the system delivers to a process. `kill` sends one, and despite its name, the one it sends unless you ask for another is the polite one, **TERM** (for *terminate*): "please finish up and stop".

Start the alarm in the second terminal with `./alarm.sh`. It prints `BEEP` every second. In the first terminal, find its PID:

```bash
ps aux | grep alarm
```

```text
sam         2565  0.0  0.0   4952  3784 pts/5    S+   07:33   0:00 bash ./alarm.sh
sam         2648  0.0  0.0   4132  2432 pts/2    S+   07:33   0:00 grep --color=auto alarm
```

Then send it TERM. `kill` prints nothing when it works:

```bash
kill 2565
```

The second terminal shows what happened. The alarm stopped, and bash reports how it ended:

```text
BEEP
BEEP
BEEP
BEEP
BEEP
Terminated                 ./alarm.sh
```

## When TERM isn't enough: -KILL

A program can catch TERM and run code of its own first: save the file you were editing, or delete its temporary files, the way `trap` did in *Cleaning Up: trap and mktemp*. It can also ignore it, and `stubborn.sh` does: its `trap` line answers TERM with a message and carries on. Start it in the second terminal with `./stubborn.sh`, find its PID the same way (here, 2676), and ask it to stop:

```bash
kill 2676
```

The second terminal answers `Not now, I am busy.`, and the program keeps going. `ps -p` (read "the process with this PID") lists only that one process, so you can check:

```bash
ps -p 2676
```

```text
    PID TTY          TIME CMD
   2676 pts/5    00:00:00 bash
```

Still there. Now send **KILL**, the one signal no program can catch or ignore. The system ends the process on the spot:

```bash
kill -KILL 2676
ps -p 2676
```

```text
    PID TTY          TIME CMD
```

Only the header: it's gone, and the second terminal says `Killed`. You'll often see this written `kill -9`, the signal's number. Why not use it every time? Because a killed program gets no chance to clean up: a half-saved file stays half saved, and temporary files stay behind. The habit administrators use is TERM first, a few seconds to finish, a check with `ps -p`, and KILL only when it's still there.

## By name: pgrep and pkill

Looking up a PID every time gets old, and it changes every time the program starts. `pgrep` finds processes by name and prints their PIDs; `-a` adds each one's command. A script runs as `bash`, so add `-f` to match against the whole command line, script name included. Start the alarm again in the second terminal, then:

```bash
pgrep -af alarm
```

```text
2781 bash ./alarm.sh
```

On a Mac, write `pgrep -lf alarm` instead: there, `-a` means something else.

`pkill` takes the same words and sends TERM to every process that matches. Afterward, `pgrep` finds nothing and prints nothing, and the second terminal says `Terminated`:

```bash
pkill -f alarm
pgrep -af alarm
```

Choose the words with care: `pkill -f alarm` stops every process with *alarm* anywhere in its command line, such as an editor with `alarm-notes.txt` open. That's why `pgrep` comes first, with the same words: it shows exactly what `pkill` would stop. You can signal only your own processes; another user's or the system's need `sudo` (*sudo and Who Owns a File*), and on a computer you share, ask whoever looks after it before you reach for that.

## What Ctrl+C really sends

Ctrl+C sends a signal too: **INT** (for *interrupt*), to the program running in front of you in that terminal. Like TERM, a program can catch it, and `trap` cleaned up after Ctrl+C that way. Every signal has a name and a number, and `kill -l` (a lowercase L, for *list*) turns numbers into names:

```bash
kill -l 2 9 15
```

```text
INT
KILL
TERM
```

| Signal | Number | Sent by | Can a program catch it? |
|---|---|---|---|
| INT | 2 | Ctrl+C | Yes |
| TERM | 15 | `kill`, `pkill` | Yes |
| KILL | 9 | `kill -KILL`, `kill -9` | No |

You'll also see the names with SIG in front, such as SIGTERM: `kill -TERM` and `kill -SIGTERM` are the same. On a Mac, `kill` works the same way in zsh; only the messages in the second terminal are worded differently.

## Try it in the terminal

[Terminal Quest: Mission Control, goal 2](https://www.ask-codey.com/games/terminal-quest/mission-control/bash/step): played right on the lesson page.

Launch day at Mission Control, and an alarm won't stop beeping. List what's running with `ps`, then stop the alarm with `kill` and its PID.

## Check yourself

2 questions to try on the lesson page.

## So what

`kill` is how you stop anything, anywhere, once you know its PID or its name: TERM first, so it can tidy up, and KILL only when it won't listen.

## Key terms

- [kill](https://www.ask-codey.com/glossary/kill)
- [Signal](https://www.ask-codey.com/glossary/signal-unix)
- [SIGTERM](https://www.ask-codey.com/glossary/sigterm)
- [SIGKILL](https://www.ask-codey.com/glossary/sigkill)
- [pgrep and pkill](https://www.ask-codey.com/glossary/pgrep-and-pkill)

## Cheat sheets

- [Stopping Things: kill and Signals](https://www.ask-codey.com/cheat-sheets/bash-kill-and-signals-cheat-sheet)

## Related

- [What's Running: ps, top and htop](https://www.ask-codey.com/lessons/bash-processes)
- [Background Jobs: &, jobs, fg and bg](https://www.ask-codey.com/lessons/bash-background-jobs)
- [Cleaning Up: trap and mktemp](https://www.ask-codey.com/lessons/bash-trap-and-temp-files)

Source: https://www.ask-codey.com/lessons/bash-kill-and-signals
