← Back to DevBytes

Zsh Scripting: Conditionals Complete Guide

Zsh Scripting: Conditionals Complete Guide

Conditionals are the backbone of any meaningful shell script. They let your code make decisions, branch logic, validate input, and respond to the environment. In Zsh, conditionals are richer and more flexible than in POSIX sh, offering powerful pattern matching, file tests, and a cleaner syntax. This guide walks you through everything you need to master conditionals in Zsh, from the basics to advanced patterns and best practices.

What Are Conditionals in Zsh?

Conditionals are constructs that evaluate expressions and execute code based on whether those expressions are true or false. Zsh supports several conditional mechanisms: the classic if/elif/else structure, the compact [[ ]] test, the legacy [ ] and test commands, the case statement, and the ternary-like (( )) arithmetic conditional. Unlike Bash, Zsh treats unquoted variables more safely in many contexts and offers extended globbing and pattern tests inside [[ ]].

Why Conditionals Matter

Without conditionals, scripts run linearly and cannot adapt to runtime conditions. Conditionals allow you to:

Mastering them means writing scripts that are robust, portable within Zsh, and easier to maintain.

The if Statement

The if statement is the most common conditional. Zsh uses then, elif, else, and fi keywords, just like Bash. The condition is typically wrapped in [[ ]] for string/file tests or (( )) for arithmetic.

#!/usr/bin/env zsh

name="Alice"

if [[ $name == "Alice" ]]; then
  print "Hello, Alice!"
elif [[ $name == "Bob" ]]; then
  print "Hello, Bob!"
else
  print "I don't know you, $name."
fi

Note that Zsh's print is a built-in alternative to echo with more consistent behavior. You can also use echo if you prefer.

The [[ ]] Test Operator

The [[ ]] construct is a Zsh (and Bash) extension that provides safer and more powerful tests than the POSIX [ ]. It supports pattern matching, regular expressions, logical operators, and avoids word-splitting pitfalls.

#!/usr/bin/env zsh

file="report.txt"

# String equality
if [[ $file == "report.txt" ]]; then
  print "Matched exact name"
fi

# Pattern matching with glob
if [[ $file == *.txt ]]; then
  print "It's a text file"
fi

# Regex matching with =~
if [[ $file =~ ^report\.[a-z]+$ ]]; then
  print "Matches regex"
fi

# Logical AND / OR
if [[ -f $file && -r $file ]]; then
  print "File exists and is readable"
fi

Inside [[ ]], variables do not need to be quoted to avoid word-splitting, although quoting is still a good habit for clarity and safety with empty values.

File and Directory Tests

Zsh supports the standard file test operators. These return true if the condition is met.

#!/usr/bin/env zsh

config="$HOME/.myapprc"

if [[ -f $config ]]; then
  print "Loading config from $config"
  source $config
else
  print "No config found, using defaults"
fi

if [[ ! -d $HOME/.cache/myapp ]]; then
  print "Creating cache directory"
  mkdir -p $HOME/.cache/myapp
fi

String Comparisons

Zsh provides several operators for comparing strings inside [[ ]]:

#!/usr/bin/env zsh

input=""

if [[ -z $input ]]; then
  print "Input is empty"
fi

a="apple"
b="banana"

if [[ $a < $b ]]; then
  print "$a comes before $b"
fi

Arithmetic Conditionals with (( ))

For numeric comparisons, the (( )) arithmetic construct is cleaner and more readable than using -eq, -lt, etc. It supports C-style operators like ==, !=, <, >, <=, >=, &&, and ||.

#!/usr/bin/env zsh

age=25

if (( age >= 18 )); then
  print "Adult"
else
  print "Minor"
fi

count=0
if (( count == 0 )); then
  print "Nothing to process"
fi

# Combined arithmetic logic
x=10
y=20
if (( x < y && y < 100 )); then
  print "Both conditions true"
fi

You can also use (( )) as a standalone command that returns a non-zero exit status when the expression evaluates to zero, which is useful in loops and short-circuit patterns.

The case Statement

The case statement is ideal when you need to compare a single value against multiple patterns. It supports glob patterns natively, making it perfect for menu systems and argument parsing.

#!/usr/bin/env zsh

os=$(uname)

case $os in
  Linux*)
    print "Running on Linux"
    ;;
  Darwin)
    print "Running on macOS"
    ;;
  FreeBSD|OpenBSD)
    print "Running on BSD"
    ;;
  *)
    print "Unknown OS: $os"
    ;;
esac

Each pattern ends with ;;. You can combine patterns with |, and the * wildcard acts as the default fallback. Zsh also supports the ;& fall-through operator and ;| for continuing to test subsequent patterns.

#!/usr/bin/env zsh

command="start"

case $command in
  start)
    print "Starting service"
    ;&  # fall through to next block
  restart)
    print "Applying config"
    ;;
  stop)
    print "Stopping service"
    ;;
esac

Short-Form Conditionals

Zsh supports compact one-liner conditionals using && and ||. These are great for simple checks but should not replace full if blocks for complex logic.

#!/usr/bin/env zsh

[[ -f /etc/hosts ]] && print "hosts file exists"
[[ -d /nonexistent ]] || print "directory missing"

# Assigning defaults
: ${EDITOR:=vim}
print "Your editor is $EDITOR"

You can also write if on a single line, though this is less readable for anything beyond trivial checks:

if [[ -z $USER ]]; then print "No user set"; fi

Nested Conditionals

Conditionals can be nested, but deep nesting hurts readability. Prefer early returns or case statements when possible.

#!/usr/bin/env zsh

validate_input() {
  local input=$1

  if [[ -z $input ]]; then
    print "Error: empty input"
    return 1
  fi

  if [[ $input =~ ^[0-9]+$ ]]; then
    if (( input > 0 && input < 100 )); then
      print "Valid number: $input"
      return 0
    else
      print "Number out of range"
      return 1
    fi
  else
    print "Not a number"
    return 1
  fi
}

validate_input "42"
validate_input "abc"
validate_input ""

Testing Command Success

Conditionals often check whether a command succeeded. Zsh evaluates the exit status of any command directly in an if statement.

#!/usr/bin/env zsh

if grep -q "root" /etc/passwd; then
  print "root user found"
fi

# Inverting a test
if ! ping -c 1 example.com &>/dev/null; then
  print "Network unreachable"
fi

Best Practices

Putting It All Together

Here is a practical example combining the major conditional constructs into a single script that validates arguments, checks the environment, and branches accordingly.

#!/usr/bin/env zsh
set -euo pipefail

usage() {
  print "Usage: $0 <build|test|deploy> [--env=dev|prod]"
  exit 1
}

# Validate argument count
if (( $# < 1 )); then
  usage
fi

action=$1
env="dev"

# Parse optional environment flag
if [[ $# -eq 2 ]]; then
  case $2 in
    --env=dev)  env="dev"  ;;
    --env=prod) env="prod" ;;
    *)          usage      ;;
  esac
fi

# Check for required tool
if ! command -v git &>/dev/null; then
  print "Error: git is required"
  exit 1
fi

# Branch on action
case $action in
  build)
    print "Building for $env"
    [[ $env == "prod" ]] && print "Stripping debug symbols"
    ;;
  test)
    if [[ -d ./tests ]]; then
      print "Running tests"
    else
      print "No tests directory found"
      exit 1
    fi
    ;;
  deploy)
    if [[ $env == "prod" && ! -f ./dist/app.tar.gz ]]; then
      print "Cannot deploy: build artifact missing"
      exit 1
    fi
    print "Deploying to $env"
    ;;
  *)
    usage
    ;;
esac

print "Done."

Conclusion

Conditionals are what transform a sequence of commands into a real program. Zsh gives you a rich toolkit for making decisions: if/elif/else for general branching, [[ ]] for safe string and file tests, (( )) for clean arithmetic comparisons, and case for pattern-based dispatch. By understanding the strengths of each construct and following best practices like preferring [[ ]], using early returns, and enabling strict mode, you can write Zsh scripts that are reliable, readable, and easy to maintain. The examples in this guide give you a solid foundation — start applying them in your own scripts and you will quickly find conditionals becoming second nature.

— Ad —

Google AdSense will appear here after approval

← Back to all articles