Arguments and Exit Codes๐
Part of a deep dive: Bash Scripting
Consult the map
-
Bash Scripting โ step 3 of 6
โ Variables and Quoting ยท you are here ยท Conditionals โ
A script that only works with hardcoded values isn't a tool โ it's a note to yourself. Arguments make scripts reusable. Exit codes make them composable: they let calling scripts, cron jobs, and monitoring systems know whether your script succeeded.
Where You Might Have Seen This๐
Windows batch files use %1, %2, %3 for positional arguments โ Bash uses $1, $2, $3. Exit codes are equally universal: every program returns one when it finishes (0 = success, non-zero = failure), and you've seen this whenever an installer aborted mid-way or a build stopped on error.
flowchart LR
C["Caller<br/>./deploy.sh production 1.4.2"] -->|"$0, $1, $2, $#, $@"| S["Your script runs"]
S -->|"exit 0"| OK["Caller sees success<br/>$? -eq 0"]
S -->|"exit 1+"| ERR["Caller sees failure<br/>$? -ne 0"]
style C fill:#2d3748,stroke:#cbd5e0,stroke-width:2px,color:#fff
style S fill:#1a202c,stroke:#cbd5e0,stroke-width:2px,color:#fff
style OK fill:#2f855a,stroke:#cbd5e0,stroke-width:2px,color:#fff
style ERR fill:#c53030,stroke:#cbd5e0,stroke-width:2px,color:#fff
Positional Arguments๐
Arguments passed on the command line are available as $1, $2, $3, and so on. Always assign them to named variables at the top of the script โ $1 is cryptic everywhere it appears; a named variable is self-documenting:
| Using Positional Arguments | |
|---|---|
- Assign
$1to a named variable immediately โ if$1appears ten lines later, nobody knows what it is. - Same for every positional argument. The rest of the script reads naturally.
| Calling the Script | |
|---|---|
- Output:
Deploying version 1.4.2 to production
The Argument Special Variables๐
-
$1,$2, ...${N}
Positional arguments.
$1is the first argument,$2the second. For argument 10 and above, braces are required:${10}. -
"$@"
All arguments as separate quoted strings โ each one intact, regardless of content. Use when iterating over a list the caller supplies, or forwarding arguments to another command.
$@ in Practice - Each argument the caller passed arrives as one intact item โ no word splitting, no surprises.
-
$#
The count of arguments. Validate it at the top of any script that requires specific input โ fail immediately with a usage message rather than running with missing values.
-
$0
The script's name as called. Use it in usage messages so the error always points to the right script, even when called via a symlink or from a different directory.
Script Name in Usage Message - Output:
Usage: ./deploy.sh <environment> <version>โ$0expands to the script name as called.
- Output:
\"$@\" vs \"$*\"
Almost always use "$@" โ each argument stays a separate quoted string. Use "$*" only when you want all arguments joined into one string for display output, never for passing to another command.
Exit Codes๐
Every command is a black box to its caller โ input goes in, an exit code comes out. Your script is no different. When a cron job, a monitoring agent, or another script runs yours, the exit code is the only signal they get back.
The standard codes:
- 0 โ success
- 1 โ general error
- 2 โ misuse of the command (wrong arguments)
- 126 โ command found but not executable
- 127 โ command not found
- 128+N โ killed by signal N
| Checking Exit Codes | |
|---|---|
- 0 if the pattern was found, 1 if not found, 2 if an error occurred.
- Non-zero โ the path doesn't exist.
Setting Your Script's Exit Code๐
Call exit 0 only if everything worked. Call exit 1 the moment you know something went wrong โ don't let the script continue:
| Returning Exit Codes | |
|---|---|
- Wrong number of arguments โ exit immediately, before doing any work.
- The check failed โ exit with a non-zero code so callers know.
- Technically redundant (a script that reaches the end exits 0), but explicit about intent.
Using Exit Codes in the Calling Script๐
| Acting on an Exit Code | |
|---|---|
- Checking
$?explicitly โ works, but$?is only valid immediately after the command. Any intervening command overwrites it. - Using the command directly as the condition โ cleaner and safer. This is the preferred style.
Real-World Argument Patterns๐
Most scripts require specific arguments. Check $# at the start and fail immediately with a usage message โ never let the script run with missing input:
When arguments are optional, use default values to fall back gracefully rather than requiring the caller to always supply everything:
| Arguments with Defaults | |
|---|---|
- If no first argument, default to
staging. - If no second argument, default to 30 seconds.
Wrapper scripts take fixed arguments for themselves and forward the rest to another command. shift consumes the fixed arguments, leaving "$@" as the remainder:
| Forwarding Arguments with shift | |
|---|---|
- Removes the first two arguments. What was
$3is now$1. "$@"now contains only the caller-supplied options, forwarded intact.
When your interface grows to flag-style arguments โ --environment production, --dry-run, --help โ that's the natural handoff point. Python's click library handles flags, validation, and --help generation in a way $1/$@ patterns can't scale to. See My Bash Script Is Getting Out of Hand.
Practice Exercises๐
Exercise 1: Write an Argument Validator
Write a script called backup.sh that:
- Requires exactly two arguments: a source directory and a destination directory
- Prints a helpful usage message to stderr and exits with code 1 if the wrong number of arguments is given
- Prints
"Backing up /source to /destination"when called correctly
Exercise 2: Exit Code Chain
Write a script called preflight.sh that checks three conditions and exits with code 1 if any fails, or code 0 if all pass:
- Argument
$1is provided (a hostname) - The
curlcommand is available (usecommand -v curl) - The hostname is reachable (use
ping -c 1 $1)
Print a specific error message for each failure.
Solution
Quick Recap๐
- Always assign
$1,$2to named variables at the top โ positional numbers are cryptic in the middle of a script $#โ argument count; validate at the start, fail immediately with a usage message"$@"โ all arguments, each properly quoted; use when passing arguments to another command$0โ the script's name; use it in usage messages- Exit codes are your script's only signal to callers โ 0 means success, non-zero means failure
- Use
if ! ./script.sh; thenrather than checking$?explicitly โ cleaner and$?can be overwritten - Write error messages to stderr:
echo "Error" >&2โ covered in Pipes and Redirection
What's Next?๐
Head to Conditionals โ if/elif/else, the [[ ]] operator, and the tests that drive the logic of every real Bash script.
Further Reading๐
Command References๐
man bashโ the "Special Parameters" section documents$@,$*,$#,$?,$$, and$0help shiftโ documentation for theshiftbuiltin
Deep Dives๐
- Bash FAQ: Capture Output and Exit Status โ Wooledge on storing command output and checking
$? - Process Exit Status โ GNU manual on how exit status flows through pipelines
Official Documentation๐
Exploring Python๐
- My Bash Script Is Getting Out of Hand โ When
$1/$@handling grows unwieldy: migrating argument-heavy scripts to Python with proper parsing and--helpoutput