Variables and Arguments
The hello.sh script in Bash Script Basics always printed the same, hardcoded message. In a real task, though, a script needs to work with different data every time — a different filename, a different user, a different date. Two mechanisms solve this: variables, which store data inside the script, and arguments, which pass data into the script from outside, from the command line. This article covers both.
What You Will Learn
By the end of this article you will be able to:
- follow the rules for declaring and using a variable;
- explain when the
${VAR}syntax is required; - capture a command's output into a variable (command substitution);
- pass arguments to a script through positional parameters (
$1,$2,$@,$#); - accept interactive input from a user with
read; - explain why quoting matters.
Declaring and Using a Variable
A variable is a storage location made of a name and a value. In Bash, the syntax for assigning a value to a variable is:
Two rules are strict here: there must be no space before or after the equals sign. MY_SHELL = "bash" (with spaces) fails, because Bash then tries to interpret MY_SHELL as a command name, with = and "bash" as its arguments.
To use a variable, put a $ in front of the name:
By convention, variable names are written in uppercase, but this isn't mandatory — a lowercase variable works exactly the same way. What matters is staying consistent within your own scripts.
A variable name can contain letters, digits, and underscores (_), but it cannot start with a digit:
# Correct
user_1="ali"
_TEMP="/tmp/data"
# Incorrect — starts with a digit
1user="ali"
# Incorrect — disallowed characters (-, @)
user-name="ali"
user@host="srv"
Curly Brace Syntax: ${VAR}
Consider the following situation:
You might expect bashing here, but the result is empty — because Bash tries to read $MY_SHELLing as one whole variable name, and no variable with that name exists. Curly braces solve the problem:
${ } marks the exact boundary of the variable name. It can always be used, but it's only required when other text immediately follows or precedes the variable. In every other case, $VAR and ${VAR} work identically.
Capturing a Command's Output into a Variable
To store a command's output in a variable, command substitution is used:
The command inside $( ) runs first, and its standard output (stdout) becomes the variable's value. Older scripts sometimes use backticks for the same purpose:
Both forms give the same result, but the backtick syntax is considered outdated: nesting it is awkward and harder to read. New scripts should use only the $( ) form.
Expanding a Variable Inside Quotes
A variable expands even inside double quotes ("..."):
Inside single quotes ('...'), nothing expands — $NAME is printed literally, character for character:
Tip
Practical rule: always use a variable inside double quotes ("$VAR"), especially when it's a file path or user-entered text. Without quotes, if $VAR's value contains a space, Bash can split it into several separate words — for example, in a command like rm $FILE, this can misinterpret a filename containing a space and delete unexpected files. rm "$FILE" protects against exactly this mistake.
Positional Parameters: Passing Arguments to a Script
Values can be passed from the command line when a script is run — these are called positional parameters, stored in special variables $0 through $9:
$0— the script itself (the path it was invoked with);$1— the first argument;$2— the second argument, and so on.
In this invocation, $0 is ./script.sh, $1 is parameter1, $2 is parameter2, and $3 is parameter3.
A practical example — an archive_user.sh script that takes a username as an argument:
#!/bin/bash
# Archives a user's home directory
# Usage: ./archive_user.sh <username>
user="$1"
tar -czf "/backup/${user}.tar.gz" "/home/${user}"
echo "Archived: /backup/${user}.tar.gz"
Notice: as soon as $1's value is read, it's stored in a meaningfully named variable (user) — this makes the rest of the script considerably easier to read, since later lines use $user, which shows immediately what it means, instead of $1.
Working with All Arguments: $@ and $#
If a script doesn't know in advance how many arguments it will receive, it needs to iterate through all of them one by one. Two special variables exist for this:
$@— gives every positional parameter (starting from $1) as a separate word;$#— gives the number of arguments.
#!/bin/bash
echo "Total number of arguments: $#"
for user in "$@"; do
echo "Archiving: $user"
tar -czf "/backup/${user}.tar.gz" "/home/${user}"
done
The for loop is covered in depth in Conditions and Loops, but the key rule here is that "$@" is always used inside double quotes ("$@", not $@) — only then does an argument containing a space (for example, "ali vali") get preserved as a single, whole element, instead of being split into two.
Note
If arguments are missing (for example, using $1 when it was never given), the script continues with an empty value — this often leads to unexpected errors. Checking the argument count is covered in Exit Codes and Error Handling.
"$@" vs "$*": the Difference That Matters
Bash has a second all-arguments variable, $*, and the difference between the two — especially once quoted — is one of the most common Bash interview questions, because getting it wrong silently corrupts argument handling.
"$@"expands to each argument as a separate quoted word:"$1" "$2" "$3"."$*"expands to all arguments joined into a single word, separated by the first character ofIFS(a space by default):"$1 $2 $3".
#!/bin/bash
# called as: ./demo.sh "ali vali" bob
printf 'with @: [%s]\n' "$@"
printf 'with *: [%s]\n' "$*"
"$@" preserved the two arguments (and kept the space inside "ali vali" intact), so printf ran twice; "$*" collapsed everything into one string, so printf ran once. The practical rule: use "$@" almost always — it's what correctly forwards a script's arguments to another command (some_tool "$@"). Reach for "$*" only when you deliberately want the arguments as one joined string (for example, building a log message). Unquoted $@ and $* behave identically and re-split on whitespace — which is exactly the word-splitting bug quoting exists to prevent.
shift: Consuming Arguments One at a Time
shift discards $1 and renumbers the rest down ($2 becomes $1, and so on), decrementing $#. It's the standard way to process a variable-length argument list, especially when the first argument is a subcommand or an option and the rest are its targets:
#!/bin/bash
action="$1" # e.g. "delete"
shift # drop the action; "$@" is now just the remaining files
for file in "$@"; do
echo "$action -> $file"
done
shift N drops the first N arguments at once. Calling shift when no arguments remain is a no-op in Bash (it returns non-zero but doesn't error), which is worth knowing under set -e.
Accepting Interactive Input from a User: read
Sometimes a script needs to ask the user directly for input while running — for example, when an argument wasn't provided. The read command is used for this:
#!/bin/bash
read -p "Enter the username: " user
tar -czf "/backup/${user}.tar.gz" "/home/${user}"
echo "Archived: /backup/${user}.tar.gz"
The -p flag shows a prompt message, and the word after it is the name of the variable the entered value is written to. read reads from standard input (stdin) — usually the keyboard, but it can also be another command's output through a pipeline, for example echo "mitch" | read -p "..." user (this case needs extra care because of how a subshell works — when read runs inside a pipeline, the variable doesn't carry over to the main shell).
Practical Scenario: Asking via read When No Argument Is Given
Task: make archive_user.sh work both with and without an argument — if $1 isn't given, ask the user for it.
#!/bin/bash
# Archives a user's home directory.
# Uses the argument if given, otherwise asks interactively.
if [ -z "$1" ]; then
read -p "Enter the username: " user
else
user="$1"
fi
tar -czf "/backup/${user}.tar.gz" "/home/${user}"
echo "Archived: /backup/${user}.tar.gz"
The [ -z "$1" ] test returns true if $1 is empty (or not given at all). This test and if syntax are explained fully in Conditions and Loops; what matters here is seeing that a single script can support both an argument and interactive input.
Common Mistakes
Leaving a Space Around the Equals Sign
Bash interprets this as calling a command named NAME with arguments = and "ali". The correct form is: NAME="ali".
Using a Variable Without Quotes
This is interpreted as two separate arguments (report and 2026.txt), and rm tries to delete both of them (if they exist) — not the single intended file. Correct: rm "$FILE".
Using $VAR Instead of ${VAR} and Merging Text into the Variable Name
The $MY_SHELLing example above — this mistake is especially common when a file extension or prefix is appended, for example writing $name_backup.tar.gz and having Bash look for a variable named name_backup.
Relying on $1 Without Checking the Argument Count
If a script relies on the $1 argument and it isn't given, the variable becomes an empty string, and later commands (like tar or rm) can run with an empty or incorrect path. Checking for this ahead of time is covered in Exit Codes and Error Handling.
Exercises
- Write a
greet.shscript that takes a name via$1and prints"Hello, <name>!". Cover the case where no argument is given, withread -p. - Write a
sysinfo.shscript that stores the output ofhostname,whoami, anddateinto three separate variables via command substitution, and prints them on one line. - Try an argument containing a space (for example,
./greet.sh "Ali Vali") — first using$1without quotes, then with quotes, and explain the difference in the result. - Write a
list_args.shscript that prints the argument count via$#, then shows each one on a separate line via"$@".
Summary
Variables store data inside a script, while positional parameters ($1, $@, $#) let you accept data from outside; read asks the user for input directly. Following quoting rules — especially when working with file paths — is a necessary part of writing a safe script. The next topic, Conditions and Loops, covers making decisions (if) based on these variables and repeating actions (for, while).