Shell Scripting & CLI Tools: From the Command Line to Real Automation
Section 4 of 18

How to Use Variables, Quoting, and Parameter Expansion in Shell Scripts

Declaring and Reading Variables

In Bash, you create a variable by assigning a value to a name:

name="Alice"
age=30
greeting="Hello, $name"

Three rules you absolutely must know:

  1. No spaces around =. name = "Alice" is a syntax error — Bash would try to run a command called name with arguments = and Alice.
  2. Variable names are case-sensitive. Name, name, and NAME are three different variables.
  3. To use a variable, prefix it with $. name is the literal text "name"; $name is the value of the variable.

To read a variable's value, use $variablename or ${variablename} (the curly braces are optional but become necessary when you need to separate the variable name from surrounding text):

filename="report"
echo "${filename}_backup.txt"   # Prints: report_backup.txt
echo "$filename_backup.txt"     # Prints: .txt (wrong! treated as $filename_backup)

Always use ${...} when there's any ambiguity. When in doubt, use the braces.

The Quoting Rabbit Hole

Quoting in Bash is one of the biggest sources of bugs for beginners, and it's worth spending real time understanding it. There are three types of quotes, and they behave very differently.

Double quotes ("...") — preserve spaces and allow variable expansion:

name="Alice Smith"
echo "Hello, $name"   # Prints: Hello, Alice Smith
echo "Path is: $HOME" # Prints: Path is: /home/alice

Single quotes ('...') — preserve everything literally, no expansion at all:

name="Alice"
echo 'Hello, $name'   # Prints: Hello, $name (literally)
echo 'No $expansion happening here'

No quotes — Bash performs word splitting and glob expansion, which leads to bugs:

name="Alice Smith"
file="*.txt"
echo $name   # Might work, but...
ls $file     # This actually expands the glob — sometimes you want that, sometimes not

The golden rule, endorsed by every experienced shell scripter: always double-quote your variable expansions. Write "$variable", not $variable. This prevents so many subtle bugs, especially with filenames that contain spaces.

# Bad: breaks if filename has spaces
rm $filename

# Good: always
rm "$filename"

Command Substitution

Command substitution lets you capture the output of a command and use it as a value:

today=$(date +%Y-%m-%d)
echo "Today is: $today"

user_count=$(who | wc -l)
echo "There are $user_count users logged in"

The modern syntax is $(command). You'll also see the older backtick syntax `command`, but $(...) is preferred because it nests cleanly:

# Nested command substitution — only works cleanly with $()
result=$(echo $(date +%Y) | tr -d '\n')

Special Variables

Bash provides several special variables that are set automatically. These are essential for real scripting:

Variable Meaning
$0 Name of the script itself
$1, $2, ... Positional parameters (command-line arguments)
$# Number of arguments passed to the script
[ $@
$* All arguments as a single word
[ $?
$$ Process ID (PID) of the current shell
$! PID of the last background command

Here's a simple script that uses these:

#!/bin/bash
echo "Script name: $0"
echo "First argument: $1"
echo "Second argument: $2"
echo "Total arguments: $#"
echo "All arguments: $@"

Run it as ./myscript.sh hello world and you get:

Script name: ./myscript.sh
First argument: hello
Second argument: world
Total arguments: 2
All arguments: hello world

Parameter Expansion: String Superpowers

Bash has a built-in set of string manipulation operations accessed through ${...} syntax. These are incredibly useful and save you from having to invoke sed or awk for simple string operations — and this is the part that surprised me when I first learned it, because most people don't realize just how much you can do without ever leaving the shell:

filename="report_2024.txt"

# Get the length of a string
echo "${#filename}"          # 15

[# Remove a suffix pattern
echo "${filename%.txt}"      # report_2024](https://www.gnu.org/software/bash/manual/html_node/Shell-Parameter-Expansion.html)

[# Remove a prefix pattern
echo "${filename#report_}"   # 2024.txt](https://www.gnu.org/software/bash/manual/html_node/Shell-Parameter-Expansion.html)

# Default values
echo "${unset_var:-default}" # default (if unset_var is unset)
name="${1:-World}"           # Use "World" if no first argument given

# Uppercase / lowercase (Bash 4+)
msg="hello world"
echo "${msg^^}"              # HELLO WORLD (all uppercase)
echo "${msg^}"               # Hello world (capitalize first letter only)

msg2="HELLO"
echo "${msg2,,}"             # hello (all lowercase)
echo "${msg2,}"              # hELLO (lowercase first letter only)

[# Substring extraction
echo "${filename:7:4}"       # 2024 (start at index 7, take 4 chars)](https://www.gnu.org/software/bash/manual/html_node/Shell-Parameter-Expansion.html)

# Replace
echo "${filename/report/summary}"   # summary_2024.txt
echo "${filename//2/X}"             # report_X0X4.txt (replace all)

These parameter expansions run inside the shell without spawning any external process, making them very fast. They're one of those features you'll use constantly once you know they exist.