Zsh Variables & Data Types - Shell Scripting Tutorial for Beginners (Lesson 7)
Video: Zsh Variables & Data Types - Shell Scripting Tutorial for Beginners (Lesson 7) by Taught by Celeste AI - AI Coding Coach
Zsh Lesson 7: Variables, Strings, Arithmetic, Environment
name=value(no spaces around=). Read with$nameor${name}. Double quotes expand variables; single quotes don't.$(( expr ))for math.exportmakes a variable available to child processes.
This lesson is the foundation of every script you'll write. Get the quoting and expansion right; everything else follows.
Declaring variables
name=Alice
age=25
city=Melbourne
No spaces around =. name = Alice errors — Zsh thinks you're trying to run a command called name with arguments.
Read the variable:
echo "Name: $name"
echo "Age: $age"
# Name: Alice
# Age: 25
Or with braces, useful when the name borders other characters:
file=report
echo "$file_backup" # variable "file_backup" → empty
echo "${file}_backup" # report_backup
Always brace when ambiguous.
Strings and quoting
fruit=apple
# Double quotes: expand $vars and $(commands)
echo "I like $fruit"
# I like apple
# Single quotes: literal — no expansion
echo 'I like $fruit'
# I like $fruit
This is the single most important thing to internalize:
- Double quotes (
"...") — expand variables and command substitutions. - Single quotes (
'...') — completely literal.
To embed a literal $ in double quotes, escape:
price=5
echo "Cost: \$${price}.00"
# Cost: $5.00
Command substitution: $(...)
echo "Today is $(date +%A)"
# Today is Wednesday
today=$(date +%Y-%m-%d)
echo $today
# 2026-05-08
$(command) runs the command and substitutes its output. Use freely:
files=$(ls *.txt | wc -l)
echo "Found $files text files"
The older form `command` (backticks) does the same but doesn't nest cleanly. Use $(...).
Always quote variable references
# Bad
file=$(echo "my file.txt")
ls $file # fails — sees two args: "my" and "file.txt"
# Good
ls "$file" # one arg: "my file.txt"
Unquoted variable references undergo word splitting and globbing. Quote unless you specifically want splitting.
Arithmetic: $(( ... ))
result=$((5 + 3))
echo "5 + 3 = $result"
# 5 + 3 = 8
echo "10 - 4 = $((10 - 4))"
echo "6 * 7 = $((6 * 7))"
echo "20 / 4 = $((20 / 4))"
echo "17 % 5 = $((17 % 5))" # modulo
Inside $(( )), you don't need $ on variable names:
a=10
b=3
echo $((a + b)) # 13
echo "$a + $b = $((a + b))"
Operators: +, -, *, /, %, ** (power), <, >, ==, !=, &&, ||, !, bitwise & | ^ << >>.
Increment and decrement
count=0
((count++))
echo "Count: $count"
# Count: 1
((count += 5))
echo $count
# 6
(( ... )) (without the $) evaluates an arithmetic expression for its side effect. Use for incrementing counters and conditional checks (lesson 9).
Floats?
Bash and Zsh's built-in arithmetic is integer only:
echo $((10 / 3))
# 3 — not 3.333...
For floats, pipe to bc or awk:
echo "scale=2; 10/3" | bc
# 3.33
echo $(awk 'BEGIN { print 10 / 3 }')
# 3.33333
Or in Zsh specifically, (( ... )) with setopt for floats — but it's simpler to call out to bc/awk for serious math.
Environment variables
Some are pre-set:
echo "Home: $HOME"
echo "User: $USER"
echo "Shell: $SHELL"
echo "PWD: $PWD"
echo "PATH: $PATH"
HOSTNAME, LANG, EDITOR, PAGER, TERM, LINES, COLUMNS — all common.
Set your own:
MY_APP=production
echo $MY_APP
This is shell-local — child processes don't see it.
export: make it global
export DATABASE_URL='postgres://localhost/mydb'
echo $DATABASE_URL
export puts the variable in the environment — every command you run inherits it.
To export an existing variable:
MY_VAR=value
export MY_VAR
Or in one line:
export MY_VAR=value
readonly: lock it
readonly PI=3.14159
echo $PI
PI=3.14 # error: read-only variable: PI
readonly prevents modification. Useful for constants.
unset: remove
temp=delete_me
echo $temp
unset temp
echo "temp is: '$temp'"
# temp is: ''
unset VAR removes the variable. Subsequent $VAR returns empty.
set -u: error on unset
echo $undefined
# (empty — no error)
set -u
echo $undefined
# zsh: undefined: parameter not set
set -u (or set -o nounset) makes Zsh error on referencing an unset variable. Highly recommended in scripts — catches typos.
To safely reference a possibly-unset variable, use ${VAR:-default}:
echo "${MISSING:-not set}"
# not set
Common parameter expansions
filename="report.tar.gz"
# Default if unset/empty
echo "${name:-anonymous}"
# Substring (zero-indexed)
echo "${filename:0:6}" # report
# Strip suffix (shortest match)
echo "${filename%.gz}" # report.tar
# Strip suffix (longest match)
echo "${filename%%.*}" # report
# Strip prefix (shortest match)
echo "${filename#*.}" # tar.gz
# Strip prefix (longest match)
echo "${filename##*.}" # gz
# Replace
echo "${filename/tar/zip}" # report.zip.gz
Pure shell — no sed/awk needed for simple manipulations.
${var:-default} is one of the most useful patterns. There's also ${var:=default} (set if unset), ${var:?message} (error if unset).
A complete script
#!/usr/bin/env zsh
set -euo pipefail
# Configuration with defaults
NAME="${NAME:-World}"
GREETING="${GREETING:-Hello}"
ITERATIONS="${ITERATIONS:-3}"
# Computed values
START=$(date +%s)
COUNT=0
while [[ $COUNT -lt $ITERATIONS ]]; do
echo "$GREETING, $NAME!"
((COUNT++))
done
ELAPSED=$(( $(date +%s) - START ))
echo "Done in ${ELAPSED}s"
Run with defaults:
./greet.sh
# Hello, World! (x3)
Override via env:
NAME=Alice GREETING=Hi ITERATIONS=5 ./greet.sh
This is the standard "configurable script" pattern.
Common stumbles
Spaces around =. name = value errors. Always name=value.
Forgetting quotes. echo $file with file="my file.txt" becomes echo my file.txt — two args. Quote everything.
Single quotes when you wanted double. echo 'Path: $HOME' literally prints $HOME. Use double quotes for interpolation.
$VAR inside $(( )). Works but unnecessary: $((a + b)), not $(( $a + $b )).
Integer-only math. $((10 / 3)) is 3. For floats, bc or awk.
export to a string. export FOO bar doesn't work. Use export FOO=bar.
unset with $. unset $var evaluates $var first. Use unset var (no $).
Variable name typo silently empty. Catch with set -u in scripts.
What's next
Lesson 8: script input. Positional $1, defaults, read, getopts.
Recap
name=value (no spaces). $name or ${name} to read. Double quotes expand, single quotes don't. $(cmd) for command substitution. $(( expr )) for integer arithmetic. export VAR makes it visible to children. readonly VAR locks. unset VAR removes. Quote variable references unless you want word splitting. Use ${var:-default} for safe access. set -euo pipefail in scripts.
Next lesson: script input.