Halloween DealsAmazon USHalloween week deals: useful picks before month-endAmazon US: current deals, useful picks and tech finds.Check DealsWindows FixRecommendedWindows errors stealing your time? Find the fix fastScan stability, cleanup and performance issues.Fix NowHalloween DealsAmazon USOctober picks are moving - compare before checkoutAmazon US: current deals, useful picks and tech finds.See Picks×
Skip to content
World desk10 min

How to Read Command-Line Arguments in Shell Scripts on Linux

A practical Linux shell guide to positional parameters, safe quoting, shift, set --, and portable getopts parsing, with Bash and POSIX examples.
Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

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.

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.

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

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.

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.

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

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.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
#!/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.

#!/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.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
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.

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

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:

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.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
#!/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.

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

After 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.

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

Running 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.

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

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.

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

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.

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.

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

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 -vq and 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.

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

Putting the pieces together

A reliable shell interface usually follows this order:

  1. Choose the interpreter in the shebang and use syntax supported by that shell.
  2. Parse conventional short options with getopts, or use a manual case/shift loop when custom long-option syntax is required.
  3. Handle unknown options and missing option arguments immediately with a clear message and status 2.
  4. Shift away parsed options so the remaining "$@" contains only operands.
  5. Validate the operand count with $# before dereferencing required positions.
  6. Quote every argument expansion when passing data to commands or functions.
  7. 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.

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.

Leave a Reply

Your email address will not be published. Required fields are marked *

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

More from the Wire

  1. World desk4 min
    How to Spot an AI Voice Scam Before Sending MoneyDon’t rely on how a caller sounds. Pause, call back through a known number, and verify the emergency with another trusted person before sending money.
  2. Mountain View desk4 min
    Google’s SynthID Detector: How to Check AI-Generated Images, Video and AudioGoogle’s SynthID Detector looks for an embedded watermark in supported images, video and audio. Here is what its results do—and do not—show.
  3. Redmond desk20 min
    How to create a link to File or Folder in Windows 11Windows 11 gives you several ways to point to a file or folder without moving or duplicating it. You can create a desktop shortcut,…
Recommended PC Tool
Recommended PC Tool
Crashes, No Sound, or Screen Glitches?Free driver scan
PC Slower Than It Used to Be?Free scan - under a minute

Two free Windows tools

One Free Minute Could Fix That PC

Before you go - each of these free tools takes about a minute and tackles what quietly slows a Windows PC down.

Special offer. View Outbyte info, uninstall instructions, EULA, and Privacy Policy.