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:
| # | Preview | Product | Price | |
|---|---|---|---|---|
| 1 |
|
Das Keyboard 4 Ultimate Blank Wired Mechanical Keyboard, Cherry MX Blue Mechanical Switches, 2-Port... | $199.00 | Buy on Amazon |
| 2 |
|
Classic Shell Scripting | $17.51 | Buy on Amazon |
| 3 |
|
Using csh & tcsh (Nutshell Handbooks) | $11.03 | Buy on Amazon |
| 4 |
|
Mac OS X Tiger: Missing Manual | $38.75 | Buy on Amazon |
| 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.
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
- 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.
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.
Recommended Free Tools
Iterate over arguments or consume them with shift
Iterate without changing the list
Use an explicit for list so the safe quoting is visible:
Rank #2
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:
PC Slower Than It Used to Be?
A free scan shows the junk files, broken settings and background clutter dragging Windows down - then fixes them in one click.Free scan · Windows 10 & 11Outdated Drivers Are Slowing You Down
One free scan finds every outdated or missing driver and matches the right update for your exact hardware.Free scan · exact hardware matchfiles=()
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:
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
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.
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[@]}".
Quick wins for a faster PC:
Clear out junk files and repair common Windows errorsFree Scan →Fix the driver behind crashes, sound loss and screen glitchesFind Drivers →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.
Rank #4
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
osays that-otakes a value;OPTARGcontains that value. OPTINDtracks the index of the next argument to process. Shifting byOPTIND - 1removes the parsed options and leaves operands in"$@".--marks the end of options in the usualgetoptsworkflow; 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:
The Tool Desk
Outbyte Driver Updater FREEScan for outdated or missing drivers - takes under a minuteDriver Scan →Outbyte PC Repair FREEClear out junk files and repair common Windows errorsFree Scan →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.
Do these 3 things before closing this tab:
1Fix the driver behind crashes, sound loss and screen glitches2Clear out junk files and repair common Windows errors3Scan for outdated or missing drivers - takes under a minutePut 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.
Quick Recap
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.

