Sagar.BlogArticle
All posts
All posts
Bash

Positional Parameters and Argument Handling

Scripts receive data from the caller via positional parameters. Learn how to access, validate, and process script arguments robustly.

January 24, 20266 min read
BashArgumentsScripting

Positional parameters are how callers pass information to your script. Understanding all the ways to access, validate, and iterate over them lets you write scripts with clean, intuitive command-line interfaces.

Accessing Arguments

#!/usr/bin/env bash
# Called as: ./script.sh one two three four five

echo "$0 = $0"      # ./script.sh  (script name)
echo "$1 = $1"      # one
echo "$2 = $2"      # two
echo "$# = $#"      # 5
echo "$@ = $@"      # one two three four five
echo "$* = $*"      # one two three four five

# Access argument 10+: use braces
# echo "${10}"       # would give the 10th argument

# All args in a loop (preserves spaces in individual args)
for arg in "$@"; do
    echo "  arg: $arg"
done

Validation Patterns

#!/usr/bin/env bash

function usage {
    cat >&2 <<-EOF
	Usage: $0 [OPTIONS] <source> <destination>

	Options:
	  -v    Verbose
	  -h    Show this help

	Arguments:
	  source       Path to input file
	  destination  Path to output directory
	EOF
    exit 1
}

# Require minimum arguments
if [[ $# -lt 2 ]]; then
    echo "ERROR: Too few arguments" >&2
    usage
fi

# Require exactly N arguments
if [[ $# -ne 2 ]]; then
    echo "ERROR: Expected exactly 2 arguments, got $#" >&2
    usage
fi

Default Argument Values

#!/usr/bin/env bash

# Set defaults if not provided
host="${1:-localhost}"
port="${2:-8080}"
timeout="${3:-30}"

echo "Connecting to $host:$port (timeout: ${timeout}s)"

# Require argument or exit with message
config="${1:?Usage: $0 <config-file>}"

# Multiple defaults in one go
input_file="${1:-/dev/stdin}"
output_dir="${2:-./output}"
log_level="${3:-info}"

Processing All Arguments

#!/usr/bin/env bash
# Script that accepts any number of files

if [[ $# -eq 0 ]]; then
    echo "Usage: $0 <file> [file...]" >&2
    exit 1
fi

processed=0
failed=0

for file in "$@"; do
    if [[ ! -f "$file" ]]; then
        echo "Skipping (not a file): $file" >&2
        (( failed++ ))
        continue
    fi
    echo "Processing: $file"
    # ... do work ...
    (( processed++ ))
done

echo ""
echo "Done: $processed processed, $failed skipped"

set -- to Replace Arguments

# set -- replaces positional parameters
set -- "new_first" "new_second" "new_third"
echo "$1"    # new_first
echo "$2"    # new_second

# Useful for normalising arguments
# E.g., split "host:port" into $1 and $2
pair="localhost:5432"
IFS=: read -r host port <<< "$pair"
set -- "$host" "$port"
echo "$1"    # localhost
echo "$2"    # 5432
Quick Check

How do you access the 11th positional argument in a Bash script?

Exercise

Write a script word-count.sh that:

  1. Accepts one or more filenames as arguments
  2. For each file: prints "filename: N words"
  3. At the end, prints the total word count across all files
  4. If a file doesn't exist, skip it with a warning