Exit Codes: Using $? and && ||

Exit Codes: Using $? and && ||

What You'll Learn

  • The idea of an exit status (exit code) that signals success or failure
  • How to check the result of the previous command with $?
  • How to branch on "if it succeeded" and "if it failed" with && and ||
  • A first step toward handling errors properly in shell scripts

Quick Summary

  • Every command returns a number when it finishes (0 means success, anything else means failure)
  • Want to see the last result? echo $?
  • Run next on success&&
  • Run next on failure||

1. What Is an Exit Status?

Conclusion: An exit status is the number a command returns. 0 means success; 1 to 255 mean failure.

Lina: Senpai, when I run a command the output shows up on screen. But how do I know whether it actually succeeded or failed?
Linny-senpai: Great question. Every command always returns one number when it finishes. We call it the exit status (or exit code).
Lina: A number? I don't see one on screen, though.
Linny-senpai: It's hidden from normal view. The rule is simple: 0 means success, anything other than 0 means failure. That single number tells you whether the command worked.

The basic rule

Number Meaning
0 Success
1255 Failure (some problem)

"0 means success" is counterintuitive, so lock it in first.

2. Why Does the Exit Status Matter?

Conclusion: The exit status is what lets you automate "run the next step only when this one succeeded."

Lina: Okay, a number is returned. But what is it actually good for?
Linny-senpai: Imagine a task like "delete the old files only after the backup succeeds." Deleting them after a failed backup would be a disaster, right?
Lina: Definitely! I'd want the next action to depend on whether the first one worked.
Linny-senpai: Exactly. That decision relies on the exit status. It's the foundation of automation and shell scripting.

Judging success only by what's printed on screen is risky. A command can fail even when no "Error" text appears. Relying on the machine-readable exit status is far more reliable.

3. Check the Result with $?

Conclusion: $? holds the exit status of the last command. Use echo $? to see it.

The exit status of the command you just ran is stored in the special variable $?.

$ ls /etc
hosts  passwd  ...
$ echo $?
0
Lina: I got 0! So ls succeeded.
Linny-senpai: Right. Now let's make it fail on purpose. Try running ls on a path that doesn't exist.
$ ls /not-exist
ls: cannot access '/not-exist': No such file or directory
$ echo $?
2
Lina: This time I got 2. A different number from before!
Linny-senpai: On failure it returns something other than 0. ls returns 2 for a "file not found" error. You don't need to memorize the exact numbers — just whether it's 0 or not.

$? only holds the result of the immediately preceding command. Running echo $? twice in a row makes the second one show "the exit status of the first echo (which succeeded, so 0)." Check it only once.

4. Chain "on success" with &&

Conclusion: cmd1 && cmd2 runs cmd2 only when cmd1 succeeds (returns 0).

&& means "if the left side succeeds, run the right side too."

$ mkdir backup && cp data.txt backup/
Lina: So this means "run cp only if mkdir succeeded"?
Linny-senpai: Exactly. If mkdir backup fails (say, you lack permission to create it), cp won't run at all. Use it when a step depends on the previous one succeeding.
# Run tests only if the build succeeds
$ make && make test

# List the contents only if the cd succeeds
$ cd /var/log && ls

Think of && as "proceed once the precondition is met." It safely chains steps where you want to stop if something fails.

5. Chain "on failure" with ||

Conclusion: cmd1 || cmd2 runs cmd2 only when cmd1 fails. Use it for error handling.

|| is the opposite of &&: it means "if the left side fails, run the right side."

$ cd /var/log || echo "Could not move into the directory"
Lina: So if cd fails it prints a message. And if it succeeds, no message?
Linny-senpai: Correct. If cd succeeds, the echo never runs. || is often used as a safety net for when things go wrong.
# Prompt to install if a command is missing
$ which jq || echo "jq is not installed. Please install it."

# Exit the script if the step fails
$ cp data.txt backup/ || exit 1

&& vs || at a glance

cmd1 && cmd2   →  cmd2 runs if cmd1 [succeeds]
cmd1 || cmd2   →  cmd2 runs if cmd1 [fails]

Remember: "&& proceeds on success," "|| proceeds on failure."

6. Combining && and ||

Conclusion: cmd && on-success || on-failure writes "do A on success, B on failure" in one line.

Chaining && and || lets you act differently on success vs. failure.

$ ping -c1 example.com > /dev/null && echo "Connection OK" || echo "Connection NG"
Lina: So I can write "print OK on success, NG on failure" in a single line!
Linny-senpai: Yes — but there's a catch. With this pattern, if the success action (echo Connection OK) itself fails, the failure action runs too. It's fine for simple messages, but for anything complex an if statement is safer.

A && B || C is not strictly identical to "if A then B else C." If B fails, C also runs. When you need reliable branching, use an if statement (see the related article).

7. Common Beginner Pitfalls

Conclusion: The easy mistakes are getting "0 means success" backwards and reading $? too late.

7-1. Remembering "0 means success" backwards

Lina: Honestly, "0 means success" still feels strange to me...
Linny-senpai: Everyone feels that way. Think "0 = no errors = zero problems" and it sticks. Picture it as a count of errors, not a test score.

7-2. Reading $? too late

$ ls /not-exist
$ pwd            # ← an extra command slipped in
$ echo $?        # this shows pwd's result (0, success)

Check $? right after the command you care about. Any command in between overwrites $?.

7-3. Returning an exit status yourself

When you want a script or command to report success or failure to its caller, use exit.

exit 0   # end as success
exit 1   # end as failure

If you call exit without a number, it returns the exit status of the last command as-is.

8. Hands-On Exercises

Conclusion: Three exercises — check, run-on-success, run-on-failure — to practice $?, &&, and ||.

Lina: I've got the concepts! I want to try it myself.
Linny-senpai: Great — I prepared three exercises. Try them in your terminal.

Exercise 1: Run any command (e.g., ls), then print its exit status right after.

Show hint

The last result lives in $?.

Sample answer
$ ls
$ echo $?

Exercise 2: Print "Created" only when mkdir test-dir succeeds.

Show hint

"Run next on success" is &&.

Sample answer
$ mkdir test-dir && echo "Created"

Exercise 3: Try to cd into a directory that doesn't exist, and print "Cannot move" if it fails.

Show hint

"Run next on failure" is ||.

Sample answer
$ cd /not-exist || echo "Cannot move"

9. Copy-Paste Templates

Conclusion: Keep the common patterns — check, on-success, on-failure, branch, and exit — within reach.

Common patterns to keep handy

# Check the last exit status
echo $?

# Run next only on success
commandA && commandB

# Run next only on failure
commandA || commandB

# A on success, B on failure (simple branch)
command && echo "OK" || echo "NG"

# Exit the script if the step fails
command || exit 1

# End a script explicitly as success / failure
exit 0   # success
exit 1   # failure

Next Reading