Bash Positional Arguments: How to Use $1, $2, $@, and shift

By 

Updated on

6 min read

Bash Positional Arguments

Positional parameters are Bash variables that store the arguments given to a script or function. They help you to write flexible and convenient scripts that can accept input from the command line.

Understanding positional arguments is essential for writing useful Bash scripts. In this guide, you will learn how to access and work with command-line arguments in your scripts. If you are new to the command line, start with our guide on basic Linux commands .

What Are Bash Positional Arguments?

In Bash documentation, these values are called positional parameters, while many users refer to them as positional arguments because they hold the arguments passed by position.

When you run a script with arguments, Bash assigns each argument to a numbered variable called a positional parameter:

Terminal
./script.sh arg1 arg2 arg3

In this example:

  • $0 contains the script name (./script.sh)
  • $1 contains the first argument (arg1)
  • $2 contains the second argument (arg2)
  • $3 contains the third argument (arg3)

Accessing Positional Parameters

Basic Parameters ($1, $2, $3, …)

The most common way to access arguments is through $1, $2, $3, and so on:

~/greet.shsh
#!/bin/bash

echo "Hello, $1!"
echo "You are from $2."

Run the script:

Terminal
./greet.sh Leah Seattle
output
Hello, Leah!
You are from Seattle.

Parameters Beyond $9

For parameters beyond $9, use curly braces:

sh
echo "The tenth argument is: ${10}"
echo "The eleventh argument is: ${11}"

If you do not use braces, $10 would be interpreted as $1 followed by a literal 0.

Special Parameter Variables

Bash provides several special variables for working with positional parameters:

VariableDescription
$0The name of the script
$1 - $9The first 9 arguments
${10}Arguments 10 and beyond
$#The number of arguments
$@All arguments as separate words
$*All arguments as a single word

The Script Name ($0)

The $0 variable contains the name of the script as it was called:

~/showname.shsh
#!/bin/bash

echo "This script is called: $0"
Terminal
./showname.sh
output
This script is called: ./showname.sh

Counting Arguments ($#)

The $# variable holds the number of arguments passed to the script:

~/count.shsh
#!/bin/bash

echo "You provided $# arguments."
Terminal
./count.sh one two three
output
You provided 3 arguments.

This is helpful when you want to check if the right number of arguments was given:

~/validate.shsh
#!/bin/bash

if [ $# -lt 2 ]; then
    echo "Usage: $0 <source> <destination>"
    exit 1
fi

echo "Copying from $1 to $2"

All Arguments ($@ and $*)

Both $@ and $* represent all arguments, but they behave differently when quoted.

Without quotes, they are identical:

sh
for arg in $@; do echo "$arg"; done
for arg in $*; do echo "$arg"; done

With quotes, they behave differently:

  • "$@" expands to "$1" "$2" "$3" (separate strings)
  • "$*" expands to "$1 $2 $3" (single string)
~/compare.shsh
#!/bin/bash

echo "Using \"\$@\":"
for arg in "$@"; do
    echo "  - $arg"
done

echo ""
echo "Using \"\$*\":"
for arg in "$*"; do
    echo "  - $arg"
done
Terminal
./compare.sh "hello world" foo bar
output
Using "$@":
  - hello world
  - foo bar

Using "$*":
  - hello world foo bar

Notice that "$@" preserved “hello world” as a single argument, while "$*" joined everything into one string.

Best practice: Use "$@" when you need to pass arguments to another command or iterate over them individually.

The shift Command

The shift built-in command removes the first positional parameter and shifts all others down by one:

  • $2 becomes $1
  • $3 becomes $2
  • And so on…
~/shift_example.shsh
#!/bin/bash

echo "Before shift:"
echo "  \$1 = $1"
echo "  \$2 = $2"
echo "  \$3 = $3"

shift

echo ""
echo "After shift:"
echo "  \$1 = $1"
echo "  \$2 = $2"
echo "  \$3 = $3"
Terminal
./shift_example.sh apple banana cherry
output
Before shift:
  $1 = apple
  $2 = banana
  $3 = cherry

After shift:
  $1 = banana
  $2 = cherry
  $3 =

Shifting Multiple Positions

You can also use shift to move the positional parameters by more than one position at a time:

sh
shift 3

Processing All Arguments with shift

The shift command is useful for processing arguments one at a time:

~/process_all.shsh
#!/bin/bash

while [ $# -gt 0 ]; do
    echo "Processing: $1"
    shift
done

echo "Done. All arguments processed."
Terminal
./process_all.sh file1.txt file2.txt file3.txt
output
Processing: file1.txt
Processing: file2.txt
Processing: file3.txt
Done. All arguments processed.

Practical Examples

Example 1: File Backup Script

Here is a script that backs up a file to a directory you choose:

~/backup.shsh
#!/bin/bash

# Check for required arguments
if [ $# -ne 2 ]; then
    echo "Usage: $0 <file> <backup_dir>"
    exit 1
fi

SOURCE="$1"
BACKUP_DIR="$2"

# Check if the source file exists
if [ ! -f "$SOURCE" ]; then
    echo "Error: File '$SOURCE' not found."
    exit 1
fi

# Create a backup directory if it does not exist
mkdir -p "$BACKUP_DIR"

# Create backup with timestamp
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
FILENAME=$(basename "$SOURCE")
cp "$SOURCE" "$BACKUP_DIR/${FILENAME}.${TIMESTAMP}.bak"

echo "Backup created: $BACKUP_DIR/${FILENAME}.${TIMESTAMP}.bak"

Create a test file and run the script:

Terminal
echo "example" > report.txt
./backup.sh report.txt backups
output
Backup created: backups/report.txt.20260429_092000.bak

Example 2: Simple Calculator

This script does simple arithmetic calculations:

~/calc.shsh
#!/bin/bash

if [ $# -ne 3 ]; then
    echo "Usage: $0 <number1> <operator> <number2>"
    echo "Operators: + - x /"
    exit 1
fi

NUM1=$1
OP=$2
NUM2=$3

case $OP in
    +) RESULT=$((NUM1 + NUM2)) ;;
    -) RESULT=$((NUM1 - NUM2)) ;;
    x) RESULT=$((NUM1 * NUM2)) ;;
    /) RESULT=$((NUM1 / NUM2)) ;;
    *) echo "Unknown operator: $OP"; exit 1 ;;
esac

echo "$NUM1 $OP $NUM2 = $RESULT"
Terminal
./calc.sh 10 + 5
./calc.sh 20 x 3
output
10 + 5 = 15
20 x 3 = 60

Example 3: Processing Command-Line Options

This script shows how to handle flags and arguments:

~/options.shsh
#!/bin/bash

VERBOSE=false
OUTPUT=""

while [ $# -gt 0 ]; do
    case $1 in
        -v|--verbose)
            VERBOSE=true
            shift
            ;;
        -o|--output)
            if [ -z "$2" ]; then
                echo "Error: --output requires a file name"
                exit 1
            fi
            OUTPUT="$2"
            shift 2
            ;;
        -h|--help)
            echo "Usage: $0 [-v] [-o output_file] [files...]"
            exit 0
            ;;
        -*)
            echo "Unknown option: $1"
            exit 1
            ;;
        *)
            break
            ;;
    esac
done

# The remaining arguments are files
if [ "$VERBOSE" = true ]; then
    echo "Verbose mode enabled"
    echo "Output file: ${OUTPUT:-stdout}"
    echo "Files to process: $@"
fi

for file in "$@"; do
    echo "Processing: $file"
done

Positional Parameters in Functions

You can use positional parameters inside functions, too. Each function gets its own set of these parameters:

~/func_params.shsh
#!/bin/bash

greet() {
    echo "Hello, $1! You are $2 years old."
}

# Script arguments
echo "Script name: $0"
echo "Script argument 1: $1"

# Function arguments (separate from script arguments)
greet "Zoe" 30
greet "Luca" 25
Terminal
./func_params.sh test_arg
output
Script name: ./func_params.sh
Script argument 1: test_arg
Hello, Zoe! You are 30 years old.
Hello, Luca! You are 25 years old.

Default Values for Parameters

You can set default values for parameters if they are not provided:

~/defaults.shsh
#!/bin/bash

NAME="${1:-Guest}"
GREETING="${2:-Hello}"

echo "$GREETING, $NAME!"
Terminal
./defaults.sh
./defaults.sh Leah
./defaults.sh Leah "Good morning"
output
Hello, Guest!
Hello, Leah!
Good morning, Leah!

The syntax ${VAR:-default} returns default if VAR is unset or empty.

Conclusion

Positional parameters let Bash scripts read command-line arguments through variables such as $1, $2, $#, and "$@". Use shift when you need to process arguments one at a time, and always validate the expected arguments before using them. For more details on executing scripts, see our guide on how to run a bash script .

Tags

Linuxize Weekly Newsletter

A quick weekly roundup of new tutorials, news, and tips.

About the authors

Dejan Panovski

Dejan Panovski

Dejan Panovski is the founder of Linuxize, an RHCSA-certified Linux system administrator and DevOps engineer based in Skopje, Macedonia. Author of 800+ Linux tutorials with 20+ years of experience turning complex Linux tasks into clear, reliable guides.

View author page