Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Some links on this page are affiliate links: if you buy through them we may earn a commission, at no extra cost to you.

Bash positional parameters are the arguments supplied to a script, function, or sourced file. Use $1, $2, and so on to read individual arguments; use $# to count them; and use quoted "$@" to pass or iterate over all of them without losing spaces, empty values, or wildcard characters.

Positional parameters at a glance

Run a script with arguments such as ./greet.sh Ada "Grace Hopper". Inside the script, Bash assigns values by position:

Parameter Meaning
$0 The script’s invocation name. It may be a relative path, an absolute path, or just the command name used to start it.
$1 through $9 The first through ninth arguments.
${10} and higher The tenth and later arguments. Braces make the parameter number unambiguous.
$# The number of positional parameters, excluding $0.
"$@" All arguments, each kept as a separate word.
"$*" All arguments joined into one word using the first character of IFS (usually a space).
shift Removes positional parameters from the front and renumbers the remaining list.

The Bash manual documents positional parameters in detail at Positional Parameters and special parameters such as $#, $@, and $* at Special Parameters.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Read and validate individual arguments

Quote parameter expansions when they represent data. Quoting prevents word splitting and pathname expansion from changing what the script receives.

#1 Best Overall
Das Keyboard 4 Ultimate Blank Wired Mechanical Keyboard, Cherry MX Blue Mechanical Switches, 2-Port USB 3.0 Hub, Volume Knob, Aluminum Top (104 Keys, Black)
  • 4 PROFESSIONAL MECHANICAL KEYBOARD WITH BLANK KEYCAPS - The thinnest mechanical keyboard in the world! The combination of tactile feel, the psycho-acoustic experience and incredible craftsmanship all deliver an unmatched typing experience that only Das Keyboard 4 offers. Type faster and longer than you ever thought possible on one of these blank babies. The Das Keyboard 4 Ultimate is a completely blank keyboard for typists and gaming enthusiasts. It feels so good, you won't want to stop.
  • PREMIUM TACTILE EXPERIENCE - Best-in-class Cherry MX Blue mechanical key switches provide tactile and audio feedback so accurate it allows you to execute every keystroke with lightning-fast precision. Factory lubricated stabilizers on large keys for smooth typing. Enjoy the tactile experience you love from a mechanical keyboard, with just enough sound to satisfy you - and not annoy your coworkers!
  • UP TO 50 MILLION KEYSTROKES - Blank keycaps with maximum durability are paired with Cherry MX Blue switches, giving your new mechanical keyboard life up to 50 million keystrokes. High-performance, gold-plated switches provide the best contact and typing experience because, unlike other metals, gold does not rust, increasing the lifespan of the switch.
  • FULL N-KEY ROLLOVER - Fast typists, productive professionals and gamers will appreciate that Das Keyboard 4 supports full NKRO over USB. No need to use a PS2 adapter anymore. Just press shift + mute to toggle to NKRO.
  • 2 PORT USB 3.0 HUB & MORE - The convenience to charge USB devices & simultaneously upload content through USB is right at your fingertips. A blazing fast 2- port USB 3.0 hub to transfer music, high resolution pics & large videos at up to 5Gb/second. That’s 10x faster than USB 2.0. Extra long 6.5ft(201cm) USB cable w/ single USB A connector. Dedicated media controls w/ LARGE VOLUME KNOB & instant sleep button. Magnetically detachable footbar ruler to raise the keyboard to an optimal 4-degrees.
printf 'first=%sn' "$1"
printf 'second=%sn' "$2"

Avoid echo $1: if the argument is hello world, it may be split into multiple words; if it contains a wildcard such as *.txt, that pattern may expand to matching filenames. printf '%sn' "$1" is a safer way to print one argument.

Check the count before using required arguments

Use $# to verify the number of arguments before reading them:

if (( $# != 2 )); then
    printf 'usage: %s SOURCE DESTn' "$0" >&2
    exit 64
fi

source_file=$1
dest_file=$2
cp -- "$source_file" "$dest_file"

This example requires exactly two arguments, gives them descriptive variable names, and quotes each value when passing it to cp. The -- marker tells cp to treat following values as operands rather than options. Many Unix utilities support this convention, but it is not a Bash feature and not every command accepts it.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Missing and empty are different

Running ./example.sh "" supplies one argument whose value is empty. Therefore (( $# == 1 )) is true, while [[ -z $1 ]] is also true. Check the count when you need to know whether an argument was supplied; check its value separately when empty input is not allowed.

Use "$@" to preserve argument boundaries

The difference between "$@" and "$*" matters whenever arguments contain spaces, empty strings, or special characters.

Form Typical result
"$@" One word for each original argument; the right default for forwarding or iterating.
"$*" One word containing all arguments joined by the first character of IFS.
$@ Unquoted expansion is subject to word splitting and pathname expansion; avoid it for argument data.
$* Unquoted expansion is also subject to splitting and pathname expansion; avoid it for argument data.

For example, invoke a script as ./show.sh "two words" "*.txt" "". A loop over "$@" sees three arguments: two words, the literal *.txt, and one empty argument.

for arg in "$@"; do
    printf 'arg=<%s>n' "$arg"
done

By contrast, "$*" collapses the list into one word. Unquoted expansions can split one argument into several and expand wildcard characters against files in the current directory. ShellCheck flags many unquoted-expansion hazards as SC2086; see its explanation at SC2086: Double quote to prevent globbing and word splitting.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Iterate over arguments or consume them with shift

Iterate without changing the list

Use an explicit for list so the safe quoting is visible:

for arg in "$@"; do
    printf '%sn' "$arg"
done

This loop runs zero times when there are no arguments and once for an empty argument. The Bash beginner guide also describes for loops and argument handling at TLDP’s Bash Beginners Guide.

Consume arguments from the front

shift discards the first positional parameter and moves the rest down: after shift, the old $2 becomes the new $1. Use shift 2 to remove two, but only when at least two remain.

while (( $# > 0 )); do
    printf 'processing: %sn' "$1"
    shift
done

A manual parser can use that pattern to handle flags, option values, and operands:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
files=()
verbose=false
output=

while (( $# > 0 )); do
    case $1 in
        --verbose)
            verbose=true
            shift
            ;;
        --output)
            if (( $# < 2 )); then
                printf '%s: --output requires a valuen' "$0" >&2
                exit 64
            fi
            output=$2
            shift 2
            ;;
        --)
            shift
            break
            ;;
        -*)
            printf '%s: unknown option: %sn' "$0" "$1" >&2
            exit 64
            ;;
        *)
            files+=("$1")
            shift
            ;;
    esac
done

for file in "${files[@]}"; do
    printf 'file: %sn' "$file"
done

The explicit count check before shift 2 prevents a missing option value from being mistaken for another argument. In this parser, -- ends option handling; any remaining operands stay in "$@" after the loop exits.

Replace or save the positional-parameter list

set -- replaces the current positional parameters. Quote each intended argument so its boundary is preserved:

set -- alpha "two words" ""
printf 'count=%dn' "$#"

This sets three parameters, including one empty argument. If a variable is meant to be one argument, use set -- "$value", not set -- $value. To save a list for later, use a Bash array rather than joining values into a string:

args=("$@")
some-command "${args[@]}"

Arrays can also be built incrementally, which is useful when options depend on script state:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
command_args=(--color=auto)

if [[ $verbose == true ]]; then
    command_args+=(--verbose)
fi

some-command "${command_args[@]}"

Expanding an array as "${array[@]}" passes each element as a separate argument. A space-separated string cannot reliably distinguish one value containing spaces from multiple values, or preserve empty values and wildcard characters.

Rank #3
Sale
Using csh & tcsh (Nutshell Handbooks)
  • Used Book in Good Condition

Forward arguments to another command

Use quoted "$@" when a wrapper should pass along the original arguments:

some-command "$@"

If the wrapper’s final action is to run that command, exec replaces the wrapper process:

exec some-command "$@"

A pass-through wrapper that treats its first argument as the command can shift it off before forwarding the rest:

What’s actually slowing this PC down?

Pick the symptom - the matching free tool is one click away.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
if (( $# == 0 )); then
    printf 'usage: %s COMMAND [ARGUMENT...]n' "$0" >&2
    exit 64
fi

command=$1
shift
exec "$command" "$@"

Do not rebuild a command from $* or use eval to execute concatenated input. Reconstructing shell text loses argument boundaries and can turn untrusted input into executable syntax. The BashFAQ discusses risks around indirect evaluation at BashFAQ/006. If a wrapper accepts arbitrary command names from untrusted users or automation, restrict permitted commands rather than assuming quoting alone makes that policy safe.

Understand function and sourced-file parameters

Functions have their own positional parameters

When a function runs, its arguments temporarily become its positional parameters. Inside the function, $1 refers to the function’s first argument, not the script’s first argument. When the function returns, the caller’s positional parameters are restored.

report() {
    printf 'function: %sn' "$FUNCNAME"
    printf 'first argument: %sn' "$1"
    printf 'argument count: %sn' "$#"
}

report "two words"

Forward the function’s arguments with the same rule used in scripts:

run_command() {
    command "$@"
}

If a function needs the script’s original list after it has been replaced by a function’s own arguments, save it first: original_args=("$@"), then pass it later with some_function "${original_args[@]}".

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Sourcing runs in the current shell

An executed script, such as ./script.sh one two, runs with its own positional parameters. A sourced file, such as source ./script.sh one two or . ./script.sh one two, runs in the current shell context using the supplied arguments. Since commands such as set -- and shift can change the caller’s positional parameters when used by sourced code, library-style files should avoid changing that list unexpectedly.

Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Parse short options with getopts

For conventional short options such as -v and -o FILE, Bash’s getopts builtin provides option parsing without manually consuming every flag. It is designed for short options, not general long-option parsing.

verbose=false
output=

while getopts ':vo:' opt; do
    case $opt in
        v)
            verbose=true
            ;;
        o)
            output=$OPTARG
            ;;
        :)
            printf '%s: option -%s requires an argumentn' "$0" "$OPTARG" >&2
            exit 64
            ;;
        ?)
            printf '%s: invalid option: -%sn' "$0" "$OPTARG" >&2
            exit 64
            ;;
    esac
done

shift "$((OPTIND - 1))"

printf 'verbose=%sn' "$verbose"
printf 'output=%sn' "$output"

for operand in "$@"; do
    printf 'operand=%sn' "$operand"
done
  • The leading colon in ':vo:' enables explicit handling of missing option arguments and invalid options.
  • The colon after o says that -o takes a value; OPTARG contains that value.
  • OPTIND tracks the index of the next argument to process. Shifting by OPTIND - 1 removes the parsed options and leaves operands in "$@".
  • -- marks the end of options in the usual getopts workflow; text after it is treated as operands.

For long options such as --output or syntax such as --output=file, use a manual case loop or a parser appropriate to the script’s interface.

Inspect arguments and diagnose edge cases

Arguments can contain spaces, tabs, newlines, quotes, wildcard characters, and leading hyphens. Quoted "$@" preserves their boundaries when you pass them onward. For debugging, printf '%q' renders values in a shell-escaped form that makes empty strings and unusual characters easier to spot:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
printf 'count=%dn' "$#"
printf 'script=%qn' "$0"

for arg in "$@"; do
    printf 'arg=%qn' "$arg"
done

When calling a command with an argument that begins with -, place -- before the data value if that command supports the marker, for example printf '%sn' -- "$value". If it does not, consult the command’s documentation for a safe way to supply that value.

For execution tracing, Bash can include the source file and line number in each trace entry:

PS4='+ ${BASH_SOURCE}:${LINENO}: '
set -x
# commands to inspect
set +x

Tracing may print command-line arguments, so do not leave it enabled around secrets or other sensitive values.

Keep Bash and POSIX shell behavior distinct

This article uses Bash syntax, including arithmetic tests, arrays, and [[ ... ]]. Do not assume those examples run unchanged under sh, dash, or another shell. If portability is a requirement, target the intended shell explicitly and check its rules against the POSIX Shell Command Language. For Bash-specific behavior, consult the Bash shell-parameter reference.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Put the pieces together

This example combines short-option parsing, a required output value, operand validation, an array, and quoted argument forwarding:

#!/usr/bin/env bash

usage() {
    printf 'usage: %s [-v] -o OUTPUT FILE...n' "$0" >&2
}

verbose=false
output=

while getopts ':vo:' opt; do
    case $opt in
        v)
            verbose=true
            ;;
        o)
            output=$OPTARG
            ;;
        :)
            printf '%s: option -%s requires an argumentn' "$0" "$OPTARG" >&2
            usage
            exit 64
            ;;
        ?)
            printf '%s: invalid option: -%sn' "$0" "$OPTARG" >&2
            usage
            exit 64
            ;;
    esac
done

shift "$((OPTIND - 1))"

if [[ -z $output ]] || (( $# == 0 )); then
    usage
    exit 64
fi

files=("$@")

if [[ $verbose == true ]]; then
    printf 'output=%qn' "$output"
    for file in "${files[@]}"; do
        printf 'input=%qn' "$file"
    done
fi

some-command -- "$output" "${files[@]}"

Replace some-command with a command whose option syntax you have verified, including whether it accepts --. The script keeps each input file as one array element, even when a filename contains spaces or wildcard characters.

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.