Advanced Bash Scripting
Special Shell Variables
Hashtag
In shell scripting, robust error handling and input validation are crucial. Earlier, we saw how $?
captures the exit status of the last command. In this guide, we’ll dive into $#
, which returns the number of positional parameters passed to your script or function. By checking $#
early, you can prevent unexpected behavior and make your scripts more reliable.
Table of Contents
- Why
$#
Matters - Basic Usage of
$#
- Positional Parameters and Empty Defaults
- Guard Clauses with
$#
- Real-World Example: Walking Calorie Expenditure Script
- Summary of Key Variables
- Links and References
Why $#
Matters
Shell scripts often rely on user-supplied arguments. If your script assumes a certain number of inputs but none (or too many) are provided, it can silently fail or produce erroneous results. Checking $#
allows you to:
- Validate inputs before executing critical logic
- Provide clear usage messages
- Exit early on misuse
Note
Always validate positional parameters to avoid silent errors and improve script maintainability.
Basic Usage of $#
Create a script called count-args.sh
:
#!/usr/bin/env bash
echo "Number of arguments: $#"
Run it with different argument counts:
$ ./count-args.sh
Number of arguments: 0
$ ./count-args.sh alpha
Number of arguments: 1
$ ./count-args.sh alpha beta
Number of arguments: 2
$ ./count-args.sh ""
Number of arguments: 1
Positional Parameters and Empty Defaults
By default, referencing an unset positional parameter expands to an empty string without an error:
#!/usr/bin/env bash
echo "First: '${1}'"
echo "Second: '${2}'"
$ ./show-params.sh hello
First: 'hello'
Second: ''
$ echo $?
0
Without validation, scripts can continue with missing data, leading to downstream failures.
Guard Clauses with $#
Validate $#
early in your script to enforce expected usage. Below are common patterns:
Pattern | Description | Example |
---|---|---|
[[ $# -ne N ]] | Exact number of arguments | if [[ $# -ne 1 ]]; then echo "Usage: $0 <arg>" >&2; exit 1; fi |
[[ $# -lt N ]] | At least N arguments | if [[ $# -lt 1 ]]; then echo "Need at least one arg" >&2; exit 1; fi |
`[[ $# -lt MIN | $# -gt MAX ]]` |
Exact Number of Arguments
#!/usr/bin/env bash
if [[ $# -ne 1 ]]; then
echo "Usage: $0 <arg>" >&2
exit 1
fi
echo "Argument received: $1"
exit 0
Minimum Number of Arguments
#!/usr/bin/env bash
if [[ $# -lt 1 ]]; then
echo "Error: At least one argument is required." >&2
exit 1
fi
echo "Received $# argument(s)."
Range of Arguments
#!/usr/bin/env bash
readonly MIN=1
readonly MAX=2
if [[ $# -lt $MIN || $# -gt $MAX ]]; then
echo "Usage: $0 <arg1> [arg2]" >&2
echo "Provide between $MIN and $MAX arguments." >&2
exit 1
fi
echo "Arguments are within the expected range."
Real-World Example: Walking Calorie Expenditure Script
Calculating calories burned from step counts is a practical script. Let’s see a flawed version and then improve it with input validation.
Flawed Version
#!/usr/bin/env bash
steps=${1}
cal_per_step=0.04
calories=$(echo "${steps} * ${cal_per_step}" | bc)
echo "Calories burned for ${steps} steps: ${calories}"
Running without arguments shows a syntax error but exits with status 0
:
$ ./calorie.sh
(standard_in) 1: syntax error
$ echo $?
0
Warning
Scripts that continue after errors can hide critical failures. Always combine guard clauses with strict error handling.
Improved Version with set -e
and terminate()
#!/usr/bin/env bash
set -e
CL_ARGS_ERROR=155
CAL_PER_STEP=0.04
terminate() {
local msg="$1"
local code="${2:-160}"
echo "Error: $msg" >&2
exit "$code"
}
# Guard clause: expect exactly one argument
if [[ $# -ne 1 ]]; then
terminate "Please provide exactly one argument (number of steps)" "$CL_ARGS_ERROR"
fi
steps=$1
calories=$(echo "$steps * $CAL_PER_STEP" | bc)
echo "Calories burned for $steps steps: $calories"
exit 0
Usage
$ ./calorie.sh
Error: Please provide exactly one argument (number of steps)
$ echo $?
155
$ ./calorie.sh 10000
Calories burned for 10000 steps: 400.00
$ echo $?
0
Summary of Key Variables
Variable | Description | Example |
---|---|---|
$? | Exit status of the last command | echo $? |
$# | Number of positional parameters passed to a script | if [[ $# -ne 1 ]]; then ... fi |
$1, $2 | Positional parameters (first, second, etc.) | echo "First: $1" |
Links and References
Watch Video
Watch video content