Bash Check Number of Arguments: A Complete Guide With Examples

Learn how to bash check number of arguments in your shell scripts using $#, if statements, and best practices to make your scripts reliable and user-friendly.


One of the first things any solid shell script needs is argument validation. If you’re writing a script that expects input and someone runs it without passing anything, it should tell them what went wrong, not silently fail or throw a cryptic error three steps later. Knowing how to bash check number of arguments is a foundational skill, and this post covers every approach you’ll need, from the simplest one-liner to more robust patterns used in production scripts.

Bash Check Number of Arguments


The Variable That Makes It Possible: $#

In bash, $# holds the number of arguments passed to a script or function. It’s a special variable, always available, and always reflects the count of positional parameters the current script or function received.

bash
#!/bin/bash
echo "You passed $# argument(s)"

Run it:

bash
$ ./myscript.sh hello world
You passed 2 argument(s)
bash
$ ./myscript.sh
You passed 0 argument(s)

That’s the core of it. Everything else is just using $# inside a conditional to decide what to do.


The Basic Pattern: Check if Arguments Were Passed

The most common check is confirming that the user passed at least one argument. Here’s the standard pattern:

bash
#!/bin/bash

if [ $# -eq 0 ]; then
    echo "Usage: $0 <filename>"
    exit 1
fi

echo "Processing: $1"

Breaking this down:

  • $# is the argument count
  • -eq 0 means “equal to zero”
  • echo "Usage: $0 <filename>" prints a helpful message; $0 is the script name itself
  • exit 1 exits with a non-zero status, signaling failure to any calling process

This is the pattern you’ll write most often. It’s clear, it’s short, and it tells the user what the script expects.


Checking for an Exact Number of Arguments

Sometimes your script needs a precise number of arguments, not just “at least one.” Use -ne (not equal) for that:

bash
#!/bin/bash

if [ $# -ne 2 ]; then
    echo "Usage: $0 <source> <destination>"
    exit 1
fi

echo "Copying $1 to $2"

If someone passes one argument or three, the check catches it. Only exactly two arguments will get through.

For a minimum number of arguments, use -lt (less than):

bash
if [ $# -lt 3 ]; then
    echo "Error: at least 3 arguments required"
    exit 1
fi

For a maximum, use -gt (greater than):

bash
if [ $# -gt 5 ]; then
    echo "Error: too many arguments (max 5)"
    exit 1
fi

Combining Minimum and Maximum Checks

When your script accepts a range of arguments, combine both checks:

bash
#!/bin/bash

if [ $# -lt 1 ] || [ $# -gt 3 ]; then
    echo "Usage: $0 <required> [optional1] [optional2]"
    exit 1
fi

echo "Got $# argument(s)"

The || operator means “or”. If the count is less than 1 OR greater than 3, the check fails. This handles scripts with required arguments and optional ones cleanly.


Using [[ ]] Instead of [ ]

You’ll see both [ ] and [[ ]] used for conditionals in bash scripts. The double bracket [[ ]] is a bash extension that’s less strict and generally safer:

bash
if [[ $# -eq 0 ]]; then
    echo "No arguments provided"
    exit 1
fi

With [ ], you need to be careful about quoting variables to avoid word splitting issues. With [[ ]], bash handles most of that for you. For arithmetic comparisons like checking $#, both work fine, but [[ ]] is the more modern convention in bash-specific scripts.


Checking Arguments Inside Functions

$# works inside functions too, but it reflects the number of arguments passed to the function, not the script. This is useful for writing reusable validation logic:

bash
#!/bin/bash

validate_args() {
    if [[ $# -lt 2 ]]; then
        echo "Function requires at least 2 arguments"
        return 1
    fi
    echo "Function got $# arguments"
}

validate_args "one"           # Fails
validate_args "one" "two"     # Passes
validate_args "one" "two" "three"  # Passes

Note the use of return 1 instead of exit 1 inside functions. exit would terminate the entire script. return exits just the function with a status code.


A Practical Script With Full Argument Validation

Here’s a real-world example that puts everything together. This script backs up a file and takes an optional destination directory:

bash
#!/bin/bash

SCRIPT_NAME=$(basename "$0")

usage() {
    echo "Usage: $SCRIPT_NAME <file> [destination_dir]"
    echo ""
    echo "  file            The file to back up (required)"
    echo "  destination_dir Where to store the backup (optional, defaults to ./backups)"
    exit 1
}

# Check argument count
if [[ $# -lt 1 ]] || [[ $# -gt 2 ]]; then
    usage
fi

SOURCE_FILE="$1"
DEST_DIR="${2:-./backups}"

# Check the file exists
if [[ ! -f "$SOURCE_FILE" ]]; then
    echo "Error: '$SOURCE_FILE' is not a valid file"
    exit 1
fi

mkdir -p "$DEST_DIR"
cp "$SOURCE_FILE" "$DEST_DIR/"
echo "Backed up '$SOURCE_FILE' to '$DEST_DIR/'"

A few things worth noting in this script:

  • The usage() function centralizes the help text so it’s easy to update
  • ${2:-./backups} uses parameter expansion to set a default value if $2 is not provided
  • Argument validation happens before any file operations, so bad input fails fast and cleanly

Using getopts for Flag-Based Arguments

If your script uses flags like -f filename or -v for verbose mode, the positional $# check gets more complex. That’s where getopts comes in:

bash
#!/bin/bash

VERBOSE=false
OUTPUT_FILE=""

while getopts "vo:" opt; do
    case $opt in
        v) VERBOSE=true ;;
        o) OUTPUT_FILE="$OPTARG" ;;
        ?) echo "Usage: $0 [-v] [-o output_file] <input>"; exit 1 ;;
    esac
done

# Shift past the options to check remaining positional arguments
shift $((OPTIND - 1))

if [[ $# -lt 1 ]]; then
    echo "Error: input file required"
    exit 1
fi

echo "Input: $1"
[[ "$VERBOSE" == true ]] && echo "Verbose mode on"
[[ -n "$OUTPUT_FILE" ]] && echo "Output: $OUTPUT_FILE"

getopts handles the flag parsing. After the while loop, shift $((OPTIND - 1)) removes the processed flags from the argument list, leaving only positional arguments. Then $# reflects just those remaining arguments.

This pattern is standard for scripts that mix flags and positional arguments. It scales cleanly as the script grows.


Common Mistakes When Checking Arguments in Bash

Not quoting $# in comparisons. For $#, this rarely causes issues because it’s always a number, but quoting variables is a good habit:

bash
if [[ "$#" -eq 0 ]]; then  # Fine

Using exit inside a function. As mentioned, exit kills the whole script. Use return inside functions.

Checking arguments too late. Validate at the top of the script, before any work starts. There’s nothing worse than a script that processes for 30 seconds before telling you it needed a second argument.

Unhelpful error messages. "Error" alone is useless. Always include what the script expected, ideally with an example invocation.

Argument handling quality directly reflects script reliability. Scripts that validate inputs properly behave predictably, which matters in automated environments and CI pipelines. This connects to the same principles behind good software testing practices where catching bad inputs early prevents downstream failures.


Quick Reference: Bash Comparison Operators for $#

Operator Meaning Example
-eq Equal to $# -eq 2
-ne Not equal to $# -ne 0
-lt Less than $# -lt 3
-le Less than or equal $# -le 5
-gt Greater than $# -gt 1
-ge Greater than or equal $# -ge 2

These operators work inside both [ ] and [[ ]]. For string comparisons you use == and !=, but for counting arguments, always use these arithmetic operators.

Bash scripting is a core skill for anyone working in DevOps, system administration, or data engineering. Argument validation is one of those habits that separates scripts written for personal use from scripts that can be handed off and run by others. If you’re building tools that interact with data pipelines or automation workflows, pairing solid bash fundamentals with knowledge of programming tools and performance testing practices gives you a well-rounded foundation for writing reliable automation.


Key Takeaways

  • $# is the bash variable that holds the number of arguments passed to a script or function
  • Use -eq 0 to check for no arguments, -ne for exact counts, -lt and -gt for ranges
  • Always print a usage message and exit 1 when argument validation fails
  • Use return instead of exit inside functions
  • getopts handles flag-style arguments; after parsing flags, shift $((OPTIND - 1)) resets $# to reflect remaining positional arguments
  • Validate arguments at the top of the script, before any other logic runs

Once you make argument checking a habit in every script you write, your scripts become more robust and far easier for others to use without needing to read the source code first.