Skip to main content

Bash Variables & Data Types - Complete Reference Guide

• 9 min read
bash variables data types bash parameters variable scope

Quick Answer: What Are Bash Variables?

Bash variables are named containers that store data values, allowing you to reuse information throughout your scripts. The simplest way to create one is name="value", then access it with $name.

Quick Comparison: Variable Storage Methods

MethodSpeedBest ForComplexity
Simple variablesFastestString/number storageVery simple
ArraysVery fastMultiple valuesSimple
Associative arraysVery fastKey-value pairsModerate
Parameter expansionFastestManipulation & extractionSimple
Environment variablesFastCross-process sharingSimple

Bottom line: Start with simple variables. Use arrays when storing multiple values, and associative arrays when you need key-value pairs.


Bash variables are the foundation of scripting, storing data and controlling script behavior. This guide covers variable assignment, data types, scoping rules, special variables, and parameter expansion techniques.

Understanding variables is critical for writing effective Bash scripts that manipulate data, make decisions, and communicate with other programs.

Table of Contents

  1. Variable Basics
  2. Variable Assignment
  3. Data Types
  4. Special Variables
  5. Parameter Expansion
  6. Variable Scope
  7. Variable Validation
  8. Arrays and Associative Arrays
  9. Best Practices
  10. Frequently Asked Questions

Variable Basics

Creating Variables

Creating a variable in Bash is straightforward—you just assign a value to a name. There’s no fancy declaration needed; Bash figures out what you’re storing based on context. This flexibility is one of Bash’s strengths, though it requires care since variables aren’t strongly typed.

In Bash, variables are created simply by assignment:

name="John"
age=30
email="john@example.com"

Notice there are no spaces around the equals sign—name = "John" would fail because Bash would interpret name as a command. This is a common gotcha for beginners coming from other languages where spacing is forgiving.

Accessing Variables

Once you’ve created a variable, retrieve its value by prefixing the name with $. This tells Bash you want the value stored in that variable, not the variable name itself. There are three syntaxes that all work the same way:

Use the $ prefix to access variable values:

name="Alice"
echo $name      # Output: Alice
echo "$name"    # Output: Alice (safer - use quotes)
echo ${name}    # Output: Alice (explicit syntax)

Here’s an important distinction: echo $name works, but echo "$name" is safer. If your variable contains spaces or special characters, unquoted variables can break your script. For example, if name="Alice Bob" and you use echo $name, it might behave unexpectedly in some contexts. Always quote variables: "$name" should be your default habit.

The ${name} syntax might look verbose, but it’s invaluable when the variable name is ambiguous—like when you want to echo a variable followed by text: echo ${name}suffix is different from echo $namesuffix (which tries to access a variable called $namesuffix).

Variable Naming Rules

Bash has specific rules about what you can name your variables. They’re fairly intuitive, but knowing them prevents frustration:

  • Must start with a letter or underscore
  • Can contain letters, numbers, underscores
  • Case-sensitive (Name, name, NAME are different)
  • Convention: Use UPPERCASE for constants, lowercase for regular variables
# Valid
my_var=10
_private=20
VAR_NAME="test"

# Invalid
2var=10        # Starts with number
my-var=10      # Contains hyphen
my var=10      # Contains space

Why these rules? The first character must be a letter or underscore because Bash needs to distinguish variable names from numbers. Hyphens look like operators to Bash (think of subtraction), so they’re forbidden. Spaces end the variable name entirely—Bash would interpret my var=10 as trying to run a command called my with arguments var=10.

When to Use Simple Variables

Simple variables work best when:

  • You’re storing a single value (string or number)
  • The value won’t change frequently during script execution
  • You need the fastest, most readable code
  • You’re new to Bash and want to keep things simple

Skip simple variables when:

  • You need to store multiple related values (use arrays instead)
  • You need key-value pairs (use associative arrays)
  • You’re working with very large amounts of data (consider files)

Variable Assignment

Variables can receive values from three primary sources: direct assignment, user input, or command output. Each approach solves different problems, and understanding when to use each makes your scripts more efficient and readable.

Basic Assignment

The simplest way to create a variable is direct assignment. You decide what value goes in, and it stays there for the rest of your script (unless you change it). This is predictable and fast—Bash doesn’t need to wait for external commands or user input.

# String
greeting="Hello, World!"

# Number
count=42

# Command output (Command Substitution)
current_date=$(date)
# or
current_date=`date`  # Older syntax, avoid

# Arithmetic
total=$((10 + 5))

Notice the $(command) syntax for running commands—this is modern Bash and preferred over backticks. The $((expression)) syntax evaluates arithmetic without needing an external calculator. Both store results directly in variables, making them very efficient.

Assignment from User Input

Sometimes your script needs to ask the user for information. The read command pauses execution and waits for the user to type something, then stores it in a variable. This is essential for interactive scripts.

# Read from stdin
read name
echo "Hello, $name"

# Read with prompt
read -p "Enter your name: " name

# Read multiple values
read first last
echo "Name: $first $last"

The -p flag shows a prompt before waiting for input—much better than leaving users confused about why the script is hanging. When you use read first last, Bash splits the input by whitespace, putting the first word in first and the second word in last. If you read only one variable with multiple inputs, everything gets stored in that one variable.

Assignment from Command Output

Many Bash scripts need to capture the output of other programs. Command substitution with $(command) captures what a program writes to the screen and stores it in a variable. This is how you integrate your scripts with existing Unix tools.

# Get all files in directory
files=$(ls /home/user/*.txt)

# Count lines in file
line_count=$(wc -l < file.txt)

# Get current user
current_user=$(whoami)

This technique is incredibly powerful—you can use any command (ls, grep, curl, custom scripts) and capture its output. Just remember that $(command) captures stdout but not stderr, and it strips the trailing newline from the output. If you need stderr too, redirect it: $(command 2>&1).


Data Types

Here’s something important to understand about Bash: it’s dynamically typed and weakly typed. That means Bash doesn’t enforce strict data types like Python or Java. Everything is technically a string, but Bash is smart enough to treat it like a number when you use it in arithmetic. This flexibility is powerful but requires care—you can accidentally do string operations on what you thought were numbers.

Strings

Strings are the most common data type in Bash. You can use single quotes or double quotes, and they behave differently—a crucial distinction many beginners miss.

# String variables
name="John"
message='Single quotes preserve literals'
phrase="Double quotes allow $variable expansion"

echo "$name"      # Output: John
echo "$phrase"    # Output: Double quotes allow John

Single quotes treat everything literally—variables won’t expand inside them. Double quotes allow variable expansion and command substitution. If you want the literal character $ without variable expansion, single quotes are your answer. This is especially important when working with file paths or URLs that might contain $.

Numbers

Bash treats numbers as strings until you do arithmetic with them. You can store a number in a variable, but Bash only knows it’s a number when you use it in an arithmetic context.

# Integer
count=42
result=$((10 + 5))

# Float (requires bc for calculations)
pi=3.14159
area=$(echo "scale=2; 3.14159 * 5 * 5" | bc)

Notice the $((10 + 5)) syntax for arithmetic—Bash evaluates it and stores the result. For decimal numbers, Bash’s built-in math is limited to integers, so you need the bc calculator for floats. This is a common gotcha: echo $((10 / 3)) returns 3, not 3.33, because Bash does integer division.

Boolean (True/False)

Bash doesn’t have a true boolean type. Instead, it uses return codes: 0 for success/true, and non-zero (usually 1) for failure/false. This can feel backwards if you’re from other languages, but it’s consistent with Unix philosophy.

# True
is_admin=0

# False
is_readonly=1

if [ $is_admin -eq 0 ]; then
  echo "Admin user"
fi

The -eq operator compares numeric values. If you’re checking a variable that acts as a boolean, you’re technically comparing numbers, not true/false values. This is why many Bash scripts use if command; then syntax—it directly checks the command’s exit code, which is more elegant than storing the result in a variable.

Type Checking

Since Bash doesn’t enforce types, you sometimes need to verify what kind of data you’re working with. This is especially important when accepting input from users or other scripts.

# Check if variable is empty
if [ -z "$var" ]; then
  echo "Variable is empty"
fi

# Check if variable is not empty
if [ -n "$var" ]; then
  echo "Variable has value"
fi

# Check if variable is numeric
if [ "$var" -eq "$var" ] 2>/dev/null; then
  echo "Variable is numeric"
fi

The -z flag checks if a string is zero-length (empty). The -n flag checks if it has content. For numeric checking, the trick [ "$var" -eq "$var" ] works because arithmetic comparison will fail if the variable isn’t a number. The 2>/dev/null redirects error messages so they don’t clutter your output.

When to Use Each Type

  • Strings: Default for everything; use when content is text or mixed
  • Numbers: Use in arithmetic operations; store age, counts, IDs
  • Booleans: Use for flags that are either on/off; explicitly store as 0 or 1 for clarity

Special Variables

Bash automatically sets certain variables that contain useful information about your script’s execution environment. These aren’t variables you create—Bash maintains them for you. Learning these special variables is essential because they’re how your script receives information from its caller and the system.

Script Arguments

When you run a script with arguments, Bash automatically breaks them apart and stores them in special variables. This is how your script knows what it’s supposed to do.

$0      # Script name
$1-$9   # First through ninth argument
$@      # All arguments (as separate strings)
$*      # All arguments (as one string)
$#      # Number of arguments

# Example:
./script.sh arg1 arg2 arg3

# Inside script:
# $0 = ./script.sh
# $1 = arg1
# $2 = arg2
# $3 = arg3
# $# = 3
# $@ = arg1 arg2 arg3

$0 is useful for error messages—you can display your script’s name without hardcoding it. $1, $2, etc. are the positional parameters. If you have more than 9 arguments (and you might), use ${10}, ${11} with braces to access them. The $@ and $* difference matters more than you’d think: $@ preserves word boundaries (better for arguments with spaces), while $* concatenates into one string. Use "$@" when passing arguments to other commands.

Exit Status

$?      # Exit code of last command (0 = success, 1-255 = error)

ls /nonexistent 2>/dev/null
if [ $? -ne 0 ]; then
  echo "ls command failed"
fi

Process IDs

$$      # Current process ID
$!      # Process ID of last background job
PPID    # Parent process ID

echo "My PID: $$"
background_command &
echo "Background PID: $!"

Shell Info

Your environment variables tell you about the system and user. These are set by the system when your shell starts, and you can reference them in your scripts for system-aware behavior.

$SHELL      # Current shell (/bin/bash)
$HOME       # Home directory
$USER       # Current user
$PWD        # Current directory
$OLDPWD     # Previous directory

echo "User: $USER"
echo "Home: $HOME"
echo "Current dir: $PWD"

These environment variables are incredibly useful for writing portable scripts. Instead of hardcoding /home/john, use $HOME. Instead of assuming you’re running as a specific user, check $USER. This makes your scripts work across different systems and user accounts.

When to Use Special Variables

Use special variables when:

  • You need to access script arguments ($1, $2, $@)
  • You want to know your script’s name for logging ($0)
  • You need system information like home directory or username
  • You want to check if a previous command succeeded ($?)

Don’t use special variables when:

  • You’re creating temporary storage (use regular variables instead)
  • You want to modify values permanently (they’re read-only for most)

Parameter Expansion

Parameter expansion is the Swiss Army knife of Bash variable manipulation. These special syntaxes let you extract parts of variables, provide fallback values, and transform content without spawning external commands. They’re pure Bash, which means they’re fast and portable.

Length

Sometimes you need to know how long a variable’s content is. The ${#variable} syntax gives you the string length instantly. This is useful for validation (is the password long enough?) or formatting.

text="Bash"
echo ${#text}           # Output: 4

array=(a b c)
echo ${#array[@]}       # Output: 3 (array length)

The @ syntax means “all elements”—that’s how you get an array’s length. Without it, you’d just get the length of the first element.

Substring Extraction

Extracting part of a string is essential for parsing filenames, URLs, or formatted data. The syntax ${text:start:length} extracts length characters starting from position start.

text="Hello, World!"

echo ${text:0:5}        # Output: Hello (5 chars from position 0)
echo ${text:7}          # Output: World! (from position 7 to end)
echo ${text: -6}        # Output: World! (last 6 chars)

Notice the space before the minus sign in ${text: -6}—without it, Bash might misinterpret it. Positions start at 0, so ${text:0:5} gets the first 5 characters. Negative indices count from the end: -6 means “6 characters from the end.”

String Replacement

Parameter expansion allows simple text replacement without external tools. This is faster and cleaner than piping to sed for straightforward substitutions.

text="cat cat cat"

# Replace first occurrence
echo ${text/cat/dog}    # Output: dog cat cat

# Replace all occurrences
echo ${text//cat/dog}   # Output: dog dog dog

# Remove all occurrences
echo ${text//cat}       # Output:   (empty, with spaces)

Single slash replaces the first match; double slash replaces all occurrences. This pattern is perfect for simple literal text replacement on strings you’ve already loaded into variables. For regex patterns or file-in-place editing, you’d use sed instead.

Default Values

Default values handle missing data gracefully. If a variable isn’t set or is empty, you can provide a fallback. This pattern prevents “undefined variable” errors and makes scripts more robust.

# Use default if unset
name=${username:-"guest"}

# Use default and set variable if unset
name=${username:="guest"}
username="$name"

# Use alternate if set
greeting=${name:+"Hello, $name"}

# Display error if unset
config_file=${1:?"Config file required"}

The :- operator gives you a default; := sets the variable to the default if it wasn’t already set; :+ uses a value only if the variable is already set; :? throws an error message if the variable is unset. These patterns make your scripts fail fast with clear error messages when they’re missing required data.

Case Conversion (Bash 4+)

Bash 4 introduced simple case conversion without external commands. The ^^ syntax converts to uppercase, ,, to lowercase, and ^ capitalizes the first character.

text="Bash Programming"

# Uppercase
echo ${text^^}          # Output: BASH PROGRAMMING

# Lowercase
echo ${text,,}          # Output: bash programming

# Capitalize first character
echo ${text^}           # Output: Bash programming

This is much faster than piping to tr or sed for simple case changes. However, it only works in Bash 4+—check your version if these don’t work.

When to Use Parameter Expansion

Use parameter expansion when:

  • You need string length, extraction, or simple replacement
  • You want fast, built-in operations (no external commands)
  • You’re working with variables you’ve already stored
  • You need default values for missing variables

Use external tools (sed, tr) when:

  • You need regex patterns or complex transformations
  • You’re editing files directly
  • You’re working with streams or pipes

Variable Scope

Local Variables

#!/bin/bash

global_var="I'm global"

function my_function() {
  local local_var="I'm local"
  global_var="Changed globally"

  echo "Inside: $local_var"
  echo "Global: $global_var"
}

my_function

echo "Outside: $local_var"      # Empty (local not accessible)
echo "Global: $global_var"      # Output: Changed globally

Global Variables

#!/bin/bash

MY_VAR="Available everywhere"

function print_var() {
  echo "In function: $MY_VAR"
}

print_var
echo "In main: $MY_VAR"

Environment Variables

Variables exported to child processes:

# Define and export
export MY_ENV_VAR="value"

# Or define then export
MY_VAR="value"
export MY_VAR

# Access in subshell or other programs
bash -c 'echo $MY_ENV_VAR'

Variable Validation

Check if Variable Exists

# Check if set
if [ -z "${var+x}" ]; then
  echo "Variable is unset"
fi

# Check if set and not empty
if [ -n "$var" ]; then
  echo "Variable has value"
fi

Validate Numeric

validate_number() {
  if [ "$1" -eq "$1" ] 2>/dev/null; then
    echo "Valid number"
    return 0
  else
    echo "Not a number"
    return 1
  fi
}

validate_number "42"      # Valid
validate_number "abc"     # Invalid

Validate Path

if [ -f "$file" ]; then
  echo "File exists"
fi

if [ -d "$dir" ]; then
  echo "Directory exists"
fi

if [ -e "$path" ]; then
  echo "Path exists (file or directory)"
fi

Arrays and Associative Arrays

Indexed Arrays

# Define array
fruits=(apple banana orange)

# Access elements
echo ${fruits[0]}       # Output: apple
echo ${fruits[1]}       # Output: banana
echo ${fruits[@]}       # Output: apple banana orange

# Array length
echo ${#fruits[@]}      # Output: 3

# Loop through array
for fruit in "${fruits[@]}"; do
  echo "$fruit"
done

Associative Arrays (Bash 4+)

# Declare associative array
declare -A person

# Set values
person[name]="John"
person[age]="30"
person[city]="New York"

# Access values
echo ${person[name]}    # Output: John

# All keys
echo ${!person[@]}      # Output: name age city

# All values
echo ${person[@]}       # Output: John 30 New York

Best Practices

1. Always Quote Variables

# Good
echo "Hello, $name"
[ -f "$file" ] && cat "$file"

# Bad (breaks with spaces)
echo Hello, $name
[ -f $file ] && cat $file

2. Use Meaningful Names

# Good
user_count=10
backup_directory="/home/backups"

# Poor
uc=10
bd="/home/backups"

3. Use Local Variables in Functions

good_function() {
  local temp_var="local"
  global_var="accessible outside"
}

bad_function() {
  temp_var="affects global scope"
}

4. Validate Input

process_file() {
  local file="$1"

  if [ -z "$file" ]; then
    echo "ERROR: File not provided" >&2
    return 1
  fi

  if [ ! -f "$file" ]; then
    echo "ERROR: File not found: $file" >&2
    return 1
  fi

  # Process file...
}

5. Use Readonly for Constants

readonly CONFIG_DIR="/etc/myapp"
readonly MAX_RETRIES=3
readonly VERSION="1.0.0"

# Prevents accidental modification
CONFIG_DIR="/tmp"  # Error: CONFIG_DIR: readonly variable

Frequently Asked Questions

Q: What’s the difference between $var and ${var}?

A: They’re equivalent; ${var} is explicit syntax, useful when the variable name is ambiguous. Example: echo ${var}name vs echo $varname (different meanings).

Q: Should I use $@ or $*?

A: Use $@ (with quotes: "$@"). It preserves arguments with spaces. $* concatenates them together.

Q: How do I make a variable readonly?

A: Use readonly VAR="value" or declare -r VAR="value". Prevents modification.

Q: What’s the difference between local and global variables?

A: Local variables (declared with local) are accessible only within a function. Global variables are accessible everywhere.

Q: Can I have variables with special characters?

A: No, variable names can only contain letters, numbers, and underscores. Use associative arrays for flexible key names.


Next Steps

Explore related topics: