In Bash, the script name is $0; the first argument is $1, the second is $2, and so on. $# tells you how many arguments were supplied. Quote "$@" when forwarding or looping over arguments, use shift as you consume them, and use the POSIX getopts utility for conventional short options such as -v and -o file.
Need a server to try this on? Antihost offers Linux VPS plans with NVMe storage, full root access and a 14-day free trial.
The interpreter named by the script’s shebang matters. Bash syntax belongs in a Bash script, while a script advertised as /bin/sh should use POSIX shell syntax. The sections below show the direct forms first, then robust option parsing, quoting rules, portability boundaries, and common failure fixes.
As an Amazon Associate I earn from qualifying purchases.
How positional parameters work
When Bash runs a script, it assigns the invocation values to positional parameters. The special parameter 0 contains the script name, and parameters 1 onward contain the arguments that followed it. If you run ./report.sh sales.csv west, the values are:
| Parameter | Value in this invocation | Meaning |
|---|---|---|
$0 |
./report.sh |
The script name as invoked |
$1 |
sales.csv |
First argument |
$2 |
west |
Second argument |
$# |
2 |
Number of arguments |
The value of $0 is normally the path or name used to start the script, not necessarily its absolute path. A script that is sourced rather than executed has different context: its positional parameters can belong to the calling shell. For ordinary executable scripts, the table above is the model to use.
#1 Best Overall
Parameters are positional, so an omitted value changes the meaning of every later position. Prefer a named local variable once you have validated an argument:
#!/usr/bin/env bash
if (( $# < 1 )); then
printf 'usage: %s FILEn' "$0" >&2
exit 2
fi
file=$1
printf 'file=%sn' "$file"
Run it as ./show-file.sh notes.txt and it prints file=notes.txt. Calling it without an argument prints a usage message to standard error and exits with status 2, a conventional status for incorrect command-line usage.
The Tool Desk
Outbyte Driver Updater FREEFix the driver behind crashes, sound loss and screen glitchesFind Drivers →Outbyte PC Repair FREEClear out junk files and repair common Windows errorsFree Scan →Checking whether arguments were supplied
Use $#, not a test of the text in $1, to check how many positional arguments arrived. This distinguishes “no argument” from a deliberately supplied empty string.
#!/usr/bin/env bash
if (( $# == 0 )); then
printf 'usage: %s FILE [FILE ...]n' "$0" >&2
exit 2
fi
printf 'received %d argument(s)n' "$#"
for file in "$@"; do
printf 'would process: %sn' "$file"
done
For one required operand, test (( $# < 1 )). For exactly two, test (( $# != 2 )). In POSIX shell syntax, use [ "$#" -lt 1 ] or [ "$#" -ne 2 ] instead of Bash arithmetic syntax.
With set -u (also called nounset), directly expanding an unset positional parameter can terminate the script. Checking the count before reading $1 avoids that failure. If an argument may be intentionally empty, test its presence by count first and then inspect its value with a quoted expansion.
Use braces for arguments 10 and higher
A positional parameter is one or more digits, but an unbraced expansion such as $10 is parsed as ${1}0: parameter 1 followed by a literal zero. Use braces whenever a parameter number has more than one digit.
Recommended Free Tools
#!/usr/bin/env bash
printf 'first: %sn' "$1"
printf 'tenth: %sn' "${10}"
printf 'eleventh: %sn' "${11}"
In most scripts, needing ten or more fixed positions is a sign that a loop, an array, or option parsing would be clearer. Braces are still required whenever you intentionally access a multi-digit positional parameter.
Preserve argument boundaries with "$@"
"$@" expands to one shell word for each original argument. That property preserves spaces, tabs, wildcard characters, and empty arguments when you pass values to another command or iterate over them.
Rank #2
#!/usr/bin/env bash
for arg in "$@"; do
printf 'arg=%sn' "$arg"
done
For example, invoking ./args.sh 'two words' '*.log' '' produces three iterations: two words, *.log, and an empty value. The wildcard is printed literally because it is inside a quoted expansion; it is not expanded against files in the current directory.
Quoted "$*" has deliberately different behavior. It produces one word containing all positional parameters, joined by the first character of IFS (a space by default). Use it only when you explicitly need a single joined string, such as displaying a human-readable summary.
| Expansion | Quoted result | Typical use |
|---|---|---|
"$@" |
One word per argument | Forwarding arguments or looping without losing boundaries |
"$*" |
One word with arguments joined by the first IFS character |
Intentional display or serialization where joining is wanted |
$@ or $* unquoted |
Subject to word splitting and pathname expansion | Usually a bug when values are data |
Always quote a single value too: use "$1", "$file", and "$OPTARG". Unquoted expansions split on whitespace and then undergo pathname expansion, so a filename containing spaces can become several arguments and a value such as *.csv can unexpectedly turn into a list of matching files.
Forwarding arguments to another command
A wrapper that should pass every argument unchanged can use:
#!/usr/bin/env bash
if (( $# == 0 )); then
printf 'usage: %s COMMAND [ARG ...]n' "$0" >&2
exit 2
fi
command "$@"
The quoted expansion keeps the boundary of every original argument. Do not write command $@; that form can split one argument into many and expand wildcard characters. If the first argument is a filename or another value that could begin with a hyphen, place -- before operands when the receiving command supports it, for example rm -- "$file". This prevents a data value from being interpreted as an option.
Do not use eval to “rebuild” a command from $@. eval reparses its text as shell code, allowing metacharacters in an argument to change the command and creating an injection risk. Keep arguments in separate shell words and invoke the command directly.
Consume arguments with shift
shift removes the leading positional parameters and renumbers the rest. After shift, the old $2 becomes the new $1, and $# decreases accordingly. This is useful for a manual parser or a loop that handles one token at a time.
#!/usr/bin/env bash
while (( $# > 0 )); do
case $1 in
--)
shift
break
;;
-v|--verbose)
printf 'verbose mode enabledn'
shift
;;
-*)
printf 'unknown option: %sn' "$1" >&2
exit 2
;;
*)
printf 'operand: %sn' "$1"
shift
;;
esac
done
for operand in "$@"; do
printf 'remaining operand: %sn' "$operand"
done
Validate a value before removing it. A parser for --output FILE must check that a second parameter exists before reading it:
Rank #3
case $1 in
--output)
if (( $# < 2 )); then
printf '%s requires a file namen' "$1" >&2
exit 2
fi
output=$2
shift 2
;;
esac
shift 2 consumes both the option and its value. Calling shift with more positions than exist is an error, so count checks belong before every multi-parameter shift.
Replace the positional-parameter list with set --
set -- value1 value2 replaces the current positional parameters with the values after --. It is the supported way to construct or reset the list; assignments such as $2=value do not assign to a positional parameter.
What’s actually slowing this PC down?
Pick the symptom - the matching free tool is one click away.
#!/usr/bin/env bash
set -- "north region" "south region"
printf 'count=%dn' "$#"
printf 'first=%sn' "$1"
printf 'second=%sn' "$2"
The -- after set tells the shell that following words are arguments even if the first replacement value starts with a hyphen. If you need to preserve the caller’s arguments while temporarily using positional parameters, copy them to an array first in Bash:
original=("$@")
set -- generated values
# Work with the temporary list here.
set -- "${original[@]}"
Inside a shell function, positional parameters refer to that function’s arguments. A function can safely use shift without changing the script’s top-level parameters. This makes small parsing functions practical, provided every expansion remains quoted.
Parse short options with POSIX getopts
Use getopts when a script accepts conventional short options such as -v, -o file, or clustered flags such as -qv. It is the portable shell interface for retrieving options and option arguments. It updates OPTIND to the next unprocessed position and places an option’s value in OPTARG.
#!/bin/sh
verbose=false
output=
while getopts ':vo:' opt; do
case $opt in
v) verbose=true ;;
o) output=$OPTARG ;;
:) printf 'option -%s requires an argumentn' "$OPTARG" >&2; exit 2 ;;
?) printf 'unknown option: -%sn' "$OPTARG" >&2; exit 2 ;;
esac
done
shift $((OPTIND - 1))
if [ "$verbose" = true ]; then
printf 'verbose mode enabledn'
fi
if [ -n "$output" ]; then
printf 'output=%sn' "$output"
fi
for operand do
printf 'operand=%sn' "$operand"
done
The optstring :vo: declares -v as a flag and -o as an option requiring an argument. The leading colon switches to explicit error reporting: a missing argument yields :, with the option name in OPTARG; an unknown option yields ?. Without the leading colon, the shell may print its own diagnostic and return ? for both cases.
Do these 3 things before closing this tab:
1Scan for outdated or missing drivers - takes under a minute2Clear out junk files and repair common Windows errors3Fix the driver behind crashes, sound loss and screen glitchesAfter the loop, OPTIND - 1 is the number of parameters consumed. Shifting by that amount leaves ordinary operands in $@. The for operand do form is POSIX shorthand for iterating over the remaining positional parameters, and it preserves each boundary just like for operand in "$@".
Typical invocations include ./convert.sh -v -o result.txt input.dat, ./convert.sh -vo result.txt input.dat, and ./convert.sh -- -filename-that-starts-with-dash. The -- marker tells option parsing to stop; the value after it is treated as an operand. Exact support for long options such as --verbose is not provided by POSIX getopts; handle those with a documented manual loop or a separate, non-portable extension only when your target shell permits it.
Choose the right parsing method
| Method | Portability | Short options | Option arguments | Long options | Whitespace preservation | Error-message burden |
|---|---|---|---|---|---|---|
Direct $1, $2 access |
POSIX and Bash | Manual | Manual | Manual | Good when every expansion is quoted | Low for fixed operands |
while/case with shift |
POSIX and Bash | Flexible | You validate and consume values | Best choice for custom or long-option syntax | Good when quoted | High; you must reject malformed input |
getopts |
Portable POSIX interface | Built in, including clusters | Built in | Not specified by POSIX | Good when OPTARG is quoted |
Clear with a leading colon and a case handler |
Use direct access for a small command with a fixed contract, such as exactly one input file. Choose getopts for standard short options and a list of operands. Write a manual loop when a project requires long options, subcommands, repeated custom switches, or syntax that getopts does not express; budget for explicit checks for missing values, unknown options, the -- terminator, and option-like filenames.
Shebangs, Bash, and /bin/sh
The first line determines which interpreter an executable is intended to use. #!/usr/bin/env bash requests Bash through the user’s PATH; #!/bin/sh requests the system’s POSIX shell interface. Bash-only syntax includes arithmetic commands such as (( ... )), arrays, [[ ... ]], and some parameter-expansion features. A script with a /bin/sh shebang should not rely on those constructs.
Windows Errors? Fix Them Before They Spread
Repair common Windows errors and clear accumulated junk for a smoother, more stable PC - no reinstall needed.Free scan · no reinstallOutdated 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 matchRunning a script explicitly can override its shebang. For example, bash script.sh arg invokes Bash even if the file says /bin/sh, while sh script.sh arg invokes the system’s sh implementation. That can expose syntax errors or subtle behavior changes. Make the shebang, syntax, and documentation agree, then make the file executable with chmod +x script.sh and run it as ./script.sh ....
The positional-parameter rules and quoted "$@" behavior are available in both Bash and POSIX shell. The arithmetic tests and array example in this article are Bash-specific; replace them with bracket tests and POSIX techniques when the script must run under /bin/sh.
Common mistakes and their fixes
Reading $1 before checking the count
A missing required argument should produce a usage message, not an obscure failure later. Test $# first, then assign file=$1.
Using $10 for the tenth argument
Write ${10}. The unbraced form means parameter 1 followed by a zero.
Free tools Windows power users keep installed
One-click scans. No signup required.
Writing for arg in $@
Use for arg in "$@". The unquoted form splits embedded whitespace and expands wildcard characters.
Using "$*" when forwarding
Use "$@" to preserve separate arguments. "$*" intentionally creates one joined word.
Best Value
Forgetting to shift after getopts
Once option parsing ends, run shift $((OPTIND - 1)). Otherwise the option tokens remain in $@ and can be mistaken for operands.
Accepting a missing option value
Declare the option with a trailing colon in the optstring, handle the : case, and exit with a usage error rather than assigning an empty value silently.
Assuming Bash syntax under sh
Check the shebang and the command used to invoke the file. Convert arithmetic and conditional syntax to POSIX forms or require Bash explicitly.
Letting data become options
Use -- before filenames where the receiving command supports it, and never concatenate untrusted arguments into a command string for eval.
A practical testing checklist
Test argument handling with values that expose boundary and quoting errors, not only with simple words:
- No arguments: verify the usage text, standard-error destination, and nonzero exit status.
- One valid operand and the exact maximum or minimum count your interface documents.
- An argument containing spaces, tabs, quotes, wildcard characters, and an empty string.
- A filename beginning with
-, supplied after--. - Unknown options, a short option missing its required value, and a value that itself begins with
-. - Clustered flags such as
-vqand an option argument supplied in the next word. - Enough arguments to exercise
${10}if the script supports fixed positional access. - Execution through the shebang and through the intended shell command, so interpreter differences are visible.
During debugging, print values with a delimiter, for example printf '<%s>n' "$arg", so an empty argument is distinguishable from a missing one. Print $# before and after a shift to confirm that the parser consumed exactly what you intended.
Quick wins for a faster PC:
Scan for outdated or missing drivers - takes under a minuteDriver Scan →Clear out junk files and repair common Windows errorsFree Scan →Fix the driver behind crashes, sound loss and screen glitchesFind Drivers →Putting the pieces together
A reliable shell interface usually follows this order:
- Choose the interpreter in the shebang and use syntax supported by that shell.
- Parse conventional short options with
getopts, or use a manualcase/shiftloop when custom long-option syntax is required. - Handle unknown options and missing option arguments immediately with a clear message and status 2.
- Shift away parsed options so the remaining
"$@"contains only operands. - Validate the operand count with
$#before dereferencing required positions. - Quote every argument expansion when passing data to commands or functions.
- Use
--at command boundaries where a filename could be interpreted as an option.
For a fixed one-file command, direct $1 access is the clearest solution. For a conventional command-line interface, getopts supplies predictable short-option parsing and leaves operands ready after one shift. For anything more elaborate, a carefully validated manual loop gives control, but the extra code is part of the interface’s correctness rather than optional decoration.
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.




