Variables and Data Types

📦 Topic 2 — Variables and Data Types

Variables are how a script remembers information. In this chapter you will learn how to create and use your own variables, how bash handles different kinds of data, and how to work with the special variables that bash sets up for you automatically — including the ones that tell you about command-line arguments and the exit status of commands.

1 — Creating and Using Variables

In bash you create a variable simply by assigning a value to a name. There is no var keyword, no type declaration — just a name, an equals sign, and a value.

🐧 Basic variable assignment and use
#!/bin/bash # Assign values — NO spaces around the = sign name="Philip" city="London" age=32 # Use a variable by prefixing its name with $ echo "Hello, my name is $name." Hello, my name is Philip. echo "I live in $city and I am $age years old." I live in London and I am 32 years old.
⚠️ No spaces around the = sign. This is the most common beginner mistake. name="Philip" assigns a variable. name = "Philip" tries to run a command called name with arguments = and "Philip" — and fails with "command not found".

Curly Braces: ${variable}

You can wrap a variable name in curly braces — ${name}. This is optional in most cases, but becomes required when the variable name is immediately followed by other characters that could be confused with part of the name.

🐧 When curly braces are necessary
fruit="apple" # Without braces — bash reads this as variable 'fruitpie' (undefined) echo "I want $fruitpie" I want # With braces — bash correctly reads 'fruit' and appends 'pie' echo "I want ${fruit}pie" I want applepie # Also useful for clarity in complex strings echo "${fruit}s are tasty" apples are tasty

Variable Naming Rules

  • Names may contain letters, digits, and underscores
  • Names must not start with a digit
  • Names are case-sensitiveName, name, and NAME are three different variables
  • By convention, lowercase for your own variables; UPPERCASE reserved for environment variables and constants
✔ Valid names
username="philip" file_count=10 _internal="ok" MAX_RETRIES=3 myVar2="test"
✘ Invalid names
2fast="no" # starts with digit my-var="no" # hyphens not allowed my var="no" # spaces not allowed $price="no" # $ is for reading, not naming

2 — Quotes and Variable Expansion

How you quote a value determines whether bash expands variables inside it. This is one of the most important distinctions to understand in bash.

Double quotes, single quotes, and no quotes
name="World" # Double quotes — variables ARE expanded echo "Hello, $name!" Hello, World! # Single quotes — variables are NOT expanded (everything is literal) echo 'Hello, $name!' Hello, $name! # No quotes — variables expand BUT word splitting applies (avoid for strings) echo Hello, $name! Hello, World! # Danger with no quotes: spaces in variables cause word splitting file="my document.txt" ls $file # treated as two arguments: 'my' and 'document.txt' ls "$file" # correct: treated as one argument
💡 Rule of thumb: Always double-quote variables when using them — "$var" — unless you have a specific reason not to. It prevents word-splitting and glob expansion from causing unexpected behaviour.

Escaping Special Characters

🐧 Using a backslash to escape characters
# Inside double quotes, use \ to escape $ or " characters price=5 echo "The cost is \$$price" The cost is $5 # To include a literal double quote inside double quotes echo "She said \"hello\"" She said "hello" # To include a literal single quote inside single-quoted string — you can't. # Instead, end the string, add an escaped quote, then resume: echo 'it'\'"'"s a trap' # messy — double quotes are usually cleaner

3 — Data Types in Bash

Bash is a weakly typed language — all variables are stored as strings internally. However, bash can treat a variable as an integer when you use arithmetic operators, and you can use the declare built-in to give variables explicit attributes.

Type / AttributeHow to createBehaviour
String (default)name="hello"Everything is a string unless you specify otherwise. Arithmetic on an unset variable returns 0.
Integerdeclare -i count=5Bash enforces integer-only values. Assigning a non-integer sets the variable to 0. Arithmetic is performed automatically.
Read-onlyreadonly MAX=100 or declare -r MAX=100Value cannot be changed after declaration. Attempting to reassign produces an error.
Exported (env var)export VAR="value" or declare -x VARVariable is passed to child processes (subshells, scripts called from this script).
Arraydeclare -a itemsIndexed array. Covered in Topic 8.
Associative arraydeclare -A mapKey-value map. Also covered in Topic 8.
🐧 declare and readonly examples
# Integer variable — arithmetic assigned directly declare -i count=10 count+=5 echo "$count" 15 # Read-only — cannot be reassigned readonly MAX_SIZE=100 MAX_SIZE=200 bash: MAX_SIZE: readonly variable # Check variable attributes with declare -p declare -p count declare -i count="15" declare -p MAX_SIZE declare -r MAX_SIZE="100"

4 — Environment Variables

Environment variables are variables that are passed down from a parent process to its child processes. Your shell session already has dozens of them set before you run a single script — they describe the system, your user account, and your preferences.

VariableContainsExample value
$HOMEYour home directory/home/philip
$USERYour usernamephilip
$PATHColon-separated list of directories searched for executables/usr/local/bin:/usr/bin:/bin
$PWDCurrent working directory/home/philip/scripts
$OLDPWDPrevious working directory/home/philip
$SHELLPath to the current shell/bin/bash
$HOSTNAMEMachine hostnameraspberrypi
$LANGLanguage / locale settingen_GB.UTF-8
$EDITORDefault text editornano
$TERMTerminal typexterm-256color
🐧 Creating and exporting your own environment variables
# A variable created normally is LOCAL — not visible to child processes greeting="hello" bash -c 'echo $greeting' # empty — child process can't see it # export makes it available to child processes export greeting="hello" bash -c 'echo $greeting' hello # List all current environment variables env # or just the ones matching a pattern env | grep "HOME" HOME=/home/philip
Changes made with export inside a script only affect that script and its children — never the parent shell that launched the script.

5 — Special Variables

Bash automatically populates a set of read-only variables that give you information about the script itself, its arguments, and the outcome of the last command. These are among the most useful variables in shell scripting.

Positional Parameters — Script Arguments

VariableContains
$0The name of the script itself (as it was called)
$1$9The 1st through 9th arguments passed to the script
${10}${N}Arguments beyond the 9th (must use curly braces)
$#The total number of arguments passed
$@All arguments as separate quoted strings — "$1" "$2" "$3" …
$*All arguments as a single string — usually less useful than $@
🐧 Positional parameters in action
#!/bin/bash # args_demo.sh echo "Script name : $0" echo "First arg : $1" echo "Second arg : $2" echo "All args : $@" echo "Arg count : $#" # Running the script: ./args_demo.sh hello world Script name : ./args_demo.sh First arg : hello Second arg : world All args : hello world Arg count : 2

Process and Status Variables

VariableContains
$?Exit status of the last command (0 = success, non-zero = failure)
$$PID (Process ID) of the current script
$!PID of the last background command (started with &)
$-Current shell option flags
🐧 Using $? to check command success
# $? holds the exit status of the LAST command that ran ls /home echo "Exit status: $?" Exit status: 0 # 0 means success ls /nonexistent_directory ls: cannot access '/nonexistent_directory': No such file or directory echo "Exit status: $?" Exit status: 2 # non-zero means failure # Practical use: check if the last command succeeded cp file.txt backup.txt if [ "$?" -eq 0 ]; then echo "Backup created successfully." else echo "Backup FAILED." fi
Read $? immediately after the command — if you run any other command first (even echo), $? will reflect that command's status instead.
$@ vs $* — The difference only matters when they are double-quoted. "$@" expands to "$1" "$2" "$3" (each argument properly quoted separately). "$*" expands to "$1 $2 $3" (all arguments joined into one string). Use "$@" when passing arguments to another command — it preserves arguments that contain spaces.

6 — Command Substitution

Command substitution lets you capture the output of a command and use it as a value — either assigning it to a variable or inserting it directly into a string.

🐧 Two syntaxes for command substitution
# Modern syntax (recommended): $( ) today=$(date +%Y-%m-%d) echo "Today is $today" Today is 2026-06-09 # Can be used directly inside a string echo "You are logged in as $(whoami) on $(hostname)" You are logged in as philip on raspberrypi # Can be nested parent_dir=$(dirname $(pwd)) echo "Parent: $parent_dir" # Old backtick syntax — still works but harder to nest and read files=`ls -1 | wc -l` echo "Files in directory: $files"
Always use $( ) rather than backticks for new code. Backtick syntax is harder to read and cannot be nested without escaping.

7 — Default Values and Unsetting Variables

Bash provides a compact syntax for supplying default values when a variable is not set or is empty. This is far cleaner than writing an if check every time.

Parameter expansion for defaults
# ${var:-default} — use default if var is unset or empty colour="" echo "Colour: ${colour:-blue}" Colour: blue # Note: colour is still empty after this — the default is only used in the expansion # ${var:=default} — use default AND assign it to var if unset or empty echo "Colour: ${colour:=blue}" Colour: blue echo "$colour" blue # Now colour has been set to "blue" # ${var:?message} — print an error message and exit if var is unset required="" echo "${required:?Error: required variable is not set}" bash: required: Error: required variable is not set # ${var:+replacement} — use replacement only if var IS set debug="true" echo "${debug:+[DEBUG MODE ON]}" [DEBUG MODE ON]
🐧 Unsetting a variable
temp="temporary value" echo "$temp" temporary value # Remove the variable entirely unset temp echo "'$temp'" '' # $temp is now completely unset (not just empty) # Check if a variable is set if [ -z "${temp+x}" ]; then echo "temp is not set" fi

8 — Quick Reference

SyntaxWhat it does
name="value"Assign a variable (no spaces around =)
$name / ${name}Read a variable's value
"$name"Read variable with word-splitting protection (always prefer this)
'$name'Literal string — no expansion
export nameMake variable available to child processes
readonly namePrevent variable from being reassigned
unset nameRemove a variable entirely
$(command)Command substitution — capture output of a command
${var:-default}Use default if var is empty/unset
${var:=default}Use default AND assign it if var is empty/unset
${var:?msg}Exit with error message if var is empty/unset
$0Script name
$1$9Positional arguments
$#Number of arguments
$@All arguments (individually quoted)
$?Exit status of last command
$$PID of current script

✏️ Exercises

Apply what you have learned in this chapter. Try each exercise yourself before looking at the sample solution.

Exercise 1
Write a script called profile.sh that stores your name, age, and favourite programming language in variables, then prints a short bio using those variables. All three pieces of data should appear in a single echo statement.
Hint: assign each value to a descriptive variable name, then use all three variables inside one double-quoted string.
Sample Solution
#!/bin/bash # profile.sh name="Philip" age=32 language="Python" echo "My name is $name, I am $age years old, and my favourite language is $language."
Exercise 2
Write a script called args_info.sh that prints: the script's own name, how many arguments were passed, the first and second argument (or the text "not provided" if they were not given), and all arguments on one line.
Hint: use $0, $#, ${1:-not provided}, ${2:-not provided}, and $@. Test it by running it with no arguments, one argument, and two arguments.
Sample Solution
#!/bin/bash # args_info.sh echo "Script name : $0" echo "Arg count : $#" echo "First arg : ${1:-not provided}" echo "Second arg : ${2:-not provided}" echo "All args : $@"
Exercise 3
Write a script called cmd_check.sh that runs the command ls /tmp and then ls /nonexistent, printing the exit status after each one with a human-readable label ("Success" or "Failed").
Hint: capture $? immediately after each command. You can use if [ "$status" -eq 0 ] to test it. Store the exit status in a variable first so it doesn't get overwritten.
Sample Solution
#!/bin/bash # cmd_check.sh ls /tmp > /dev/null 2&1 status=$? if [ "$status" -eq 0 ]; then echo "ls /tmp → Success (exit $status)" else echo "ls /tmp → Failed (exit $status)" fi ls /nonexistent > /dev/null 2&1 status=$? if [ "$status" -eq 0 ]; then echo "ls /nonexistent → Success (exit $status)" else echo "ls /nonexistent → Failed (exit $status)" fi

> /dev/null 2>&1 silences the command's output so only your custom messages appear. Redirection is covered fully in Topic 3.

Exercise 4
Write a script called snapshot.sh that captures the current date, the current user, the current directory, and the number of files in the current directory into variables using command substitution, then prints a formatted summary. Run it from a couple of different directories to verify it works correctly each time.
Hint: use $(date +"%Y-%m-%d %H:%M"), $(whoami), $(pwd), and $(ls -1 | wc -l) to populate your variables.
Sample Solution
#!/bin/bash # snapshot.sh — captures a point-in-time snapshot timestamp=$(date +"%Y-%m-%d %H:%M") current_user=$(whoami) current_dir=$(pwd) file_count=$(ls -1 | wc -l) echo "────────────────────────────" echo " Snapshot: $timestamp" echo " User : $current_user" echo " Dir : $current_dir" echo " Files : $file_count" echo "────────────────────────────"