# Did It Work? Exit Codes, && and ||

Every command ends with an exit code, 0 when it worked and another number when it didn't, which $? shows; && runs the next command only if the last one worked, || only if it failed, and ; either way.

Apprentice · Unix · Linux · Updated November 10, 2026

## After this lesson you can

- Read a command's exit code with $?, where 0 means it worked
- Chain commands with && to run the next only if one worked, || to run it only if one failed, and ; to run it either way

## Key takeaways

- Every command ends with an exit code. 0 means it worked; any other number means it didn't, and the number can say why.
- echo $? shows the exit code of the command that ran last.
- a && b runs b only if a worked: read && as "and then, if it worked".
- a || b runs b only if a failed: read || as "or else".
- a ; b runs b either way. Use && when the second command depends on the first, such as cd and then rm.

You want to back up the edited photos and then tidy up, but only if the backup worked. Run the two commands one after another without looking, and a failed copy followed by a cleanup loses the photos. Every program already tells the shell whether it worked, and bash lets one command depend on the answer. People who look after servers chain commands this way every day ("update, and only if that worked, restart"), and every script that runs unattended at night depends on it.

The examples use the practice folder from *Keyboard Shortcuts at the Prompt*: `cd ~/terminal-practice/photo-project`.

## Every command reports back: $?

When a command finishes, it hands the shell a number, its **exit code**, like a delivery driver's note: 0 means "delivered", and any other number means something went wrong. The screen doesn't show it, but `$?` holds it. `$?` is read "dollar question mark": the value of `?`, which bash fills in after every command.

```bash
ls notes.txt
echo $?
ls holiday.txt
echo $?
```

```text
notes.txt
0
ls: cannot access 'holiday.txt': No such file or directory
2
```

The number for a failure differs from command to command and from system to system: on a Mac, `ls` gives 1 for the same mistake. Only 0 always means "it worked". Some commands give each number a meaning. `grep -q` (*Finding Text: grep*) prints nothing and answers with its exit code: 0 found it, 1 didn't, 2 couldn't read the file.

```bash
grep -q IMG_0501 notes.txt
echo $?
grep -q IMG_0502 notes.txt
echo $?
grep -q IMG_0502 holiday.txt
echo $?
```

```text
0
1
grep: holiday.txt: No such file or directory
2
```

`$?` changes after every command, `echo` included, so check it straight away.

## &&: and then, if it worked

`&&` joins two commands, and the second runs only if the first gave 0. Read it "and then, if it worked". Make a backup folder, copy the beach photos into it, and say so only when both worked:

```bash
mkdir -p backup && cp edited/2026/05-may/beach/*.jpg backup/ && echo "Backup done"
ls backup
```

```text
Backup done
IMG_0501-edit.jpg  IMG_0503-edit.jpg
```

Now a copy that fails, from a month with no folder yet:

```bash
cp edited/2026/07-july/*.jpg backup/ && echo "Backup done"
```

```text
cp: cannot stat 'edited/2026/07-july/*.jpg': No such file or directory
```

`cp` failed, so `echo` never ran. In place of `echo`, the next step could be the cleanup, and it would be skipped too, which is what you want. Joining commands with `&&`, `||` or `;` is called **command chaining**.

## ||: or else

`||` is the opposite: the second command runs only if the first failed. Read it "or else". Add a reminder to the notes, but only if it isn't there yet:

```bash
grep -q IMG_0502 notes.txt || echo 'Beach: IMG_0502 still to edit' >> notes.txt
cat notes.txt
```

```text
Spring photo show: three photos for the web page
Beach: IMG_0501 and IMG_0503 are edited
Beach: IMG_0502 still to edit
```

Run the same line again, and `grep` finds the reminder, so nothing is added: the line is safe to run twice.

## ;: either way

`;` runs the next command whatever happened. That's fine for two jobs that don't depend on each other, such as `ls exports; ls backup`. It's dangerous when they do. Mistype a folder:

```bash
cd exprots; ls
```

```text
bash: cd: exprots: No such file or directory
backup  edited  exports  notes.txt  raw
```

`cd` failed, and `ls` ran anyway, in the folder you were still in. Picture `rm *.jpg` in place of `ls`. With `&&`, a failed `cd` stops the line:

```bash
cd exprots && ls
```

```text
bash: cd: exprots: No such file or directory
```

`&&` and `||` can share a line, as a quick "if it worked, this, or else that":

```bash
grep -q IMG_0620 notes.txt && echo "0620 is in the notes" || echo "0620 isn't in the notes"
```

```text
0620 isn't in the notes
```

Keep the middle command something that can't fail, such as `echo`: if it failed, the `||` part would run too. `&&`, `||` and `;` work the same in zsh.

## Try it in the terminal

[Terminal Quest: The Clockwork Workshop, goal 7](https://www.ask-codey.com/games/terminal-quest/workshop/bash/step): played right on the lesson page.

Your turn: the gearbox is ready. Run it, and only if it works, ring the clock tower bell. Read the bell before you ring it.

## Check yourself

2 questions to try on the lesson page.

## So what

`$?`, `&&`, `||` and `;` let a line of commands check its own work: the next step runs only when it should, and a mistyped folder never leads to a command in the wrong place.

## Key terms

- [Exit Code](https://www.ask-codey.com/glossary/exit-code)
- [Command Chaining](https://www.ask-codey.com/glossary/command-chaining)

## Cheat sheets

- [Did It Work? Exit Codes, && and ||](https://www.ask-codey.com/cheat-sheets/bash-exit-codes-cheat-sheet)

## Related

- [Keyboard Shortcuts at the Prompt](https://www.ask-codey.com/lessons/bash-keyboard-shortcuts)
- [Finding Text: grep](https://www.ask-codey.com/lessons/bash-grep)

Source: https://www.ask-codey.com/lessons/bash-exit-codes
