Variables, arguments & input

Key takeaways Three things turn a hard-coded script into a flexible one: variables (NAME=value, read back as $NAME), arguments ($1, $@ — what the user types after the script name), and read (asking the user a question interactively). And one rule that saves hours of debugging: always quote your variables"$NAME". If scripts are new to you, start with your first shell script.

A script that always does exactly the same thing is barely more than a note to yourself. The moment you want it to act on a file you name, a value you type, or today’s date, you need variables, arguments, and input. They are what let one script serve a hundred situations.

Variables

A variable is a name with a value attached. Create one by writing the name, an equals sign, and the value — no spaces around the =:

name=Matt

Read it back by putting a $ in front. Use ${name} with braces when the name butts up against other text:

echo "$name"
echo "${name}_backup"

The spaces rule is strict: name = Matt (with spaces) is not an assignment — the shell reads it as a command called name. Keep the = tight.

A variable made this way is local to the running script: it lives while the script runs and is gone when it finishes. (To hand a value down to programs the script launches, you’d export it — see environment variables.)

One habit to build now: always quote a variable when you use it"$name", not $name. Quoting keeps a value that contains spaces in one piece and stops the shell from trying to expand it. The wildcards & expansion lesson shows exactly what goes wrong when you don’t.

Script arguments

When you run a script, anything you type after its name is passed in as positional parameters. Inside the script they arrive as numbered variables:

  • $1, $2, $3 … — the first, second, third argument, and so on.
  • $0 — the name the script was invoked as.
  • $@ — all the arguments together.
  • $# — the count of arguments.

Here’s a script that greets whoever you name on the command line:

#!/usr/bin/env bash
echo "Hello, $1! You passed $# argument(s)."

Save it as greet.sh, make it executable, and run it:

./greet.sh world

That prints Hello, world! You passed 1 argument(s).$1 picked up world. Run it with no arguments and $1 is simply empty, which is exactly the case the next section handles.

Reading input

Arguments come from the command line; read asks the user while the script is running. read pauses, waits for a line of typing, and stores it in a variable:

#!/usr/bin/env bash
read name
echo "Hello, $name!"

To print a prompt on the same line first, use read -p:

read -p "What is your name? " name
echo "Hello, $name!"

The script stops at the read, shows What is your name? , and waits. Whatever the user types lands in name.

Command substitution

Often the value you want is the output of another command. Command substitution runs a command and captures its output as text. Write it as $(command):

today=$(date +%F)
echo "Today is $today"

date +%F prints something like 2026-07-17, and $( ) catches that text and stores it in today. You can drop a substitution anywhere a value is expected:

echo "There are $(ls | wc -l) items here."

An older form uses backticks — today=`date +%F` — and does the same thing, but backticks nest awkwardly and are easy to misread, so $( ) is the modern default.

A small useful script

Put the pieces together: take a name as an argument, fall back to a default if none is given, and stamp the greeting with today’s date via command substitution:

#!/usr/bin/env bash
# Greet someone, defaulting to "friend" if no name is given.
name="${1:-friend}"
today=$(date +%F)
echo "Hello, $name — today is $today."

The trick is ${1:-friend}: “use $1, but if it’s unset or empty, use friend instead.” Run ./greet.sh Matt and it greets Matt; run it with no argument and it greets friend. One script, both cases handled.

Quoting matters

This is the mistake that bites everyone eventually. An unquoted variable is split on spaces and then checked for wildcards before your command ever sees it. Say a file is called my notes.txt:

file="my notes.txt"
rm $file      # WRONG: runs rm "my" "notes.txt" — two files!
rm "$file"    # RIGHT: one argument, the real filename

Unquoted, the space splits $file into two arguments and rm tries to delete my and notes.txt. Quoted, it stays one filename. The rule is simple: double-quote your variables by default"$file", "$1", "$name" — and only leave the quotes off when you have a specific reason. The same splitting and globbing behaviour is explained in full under environment variables.

Quick check: inside a script, which variable holds the first argument passed on the command line?

Recap

  • Set a variable with NAME=value (no spaces around =) and read it with $NAME or ${NAME}; it’s local to the running script.
  • Command-line arguments arrive as positional parameters: $1, $2, $0 (the script name), $@ (all of them), $# (the count).
  • read VAR prompts the user interactively; read -p "…" shows a prompt first.
  • $(command) captures a command’s output into a value — e.g. today=$(date +%F).
  • ${1:-default} supplies a fallback when an argument is missing.
  • Always double-quote your variables"$NAME" — so values with spaces survive and don’t get globbed.

Next up: conditionals & loops