2024-05-20
6 min read

How to Check if a Directory Exists in Bash Shell Scripts

How to Check if a Directory Exists in Bash Shell Scripts

Checking whether a directory exists is one of the most common tasks in Bash scripting. Whether you're creating backup scripts, setting up application environments, or managing file operations, verifying directory existence prevents errors and ensures your scripts run smoothly. Bash provides several ways to test for directory existence, each with specific use cases and advantages.

Prerequisites

You'll need basic familiarity with Bash scripting and command-line operations. A Unix-like system (Linux, macOS, or WSL) is required to test the examples.

The Standard Test Command Approach

The most common and portable method uses the test command with the -d flag:

#!/bin/bash

directory="/home/user/documents"

if [ -d "$directory" ]; then
    echo "Directory $directory exists"
else
    echo "Directory $directory does not exist"
fi

The -d test returns true if the specified path exists and is a directory. Always quote your variables to handle paths with spaces correctly.

Using the Double Bracket Syntax

Bash provides enhanced test syntax with double brackets, which offers better readability and additional features:

#!/bin/bash

check_directory() {
    local dir_path="$1"

    if [[ -d "$dir_path" ]]; then
        echo "✓ Directory exists: $dir_path"
        return 0
    else
        echo "✗ Directory not found: $dir_path"
        return 1
    fi
}

# Test multiple directories
directories=(
    "/tmp"
    "/home/user/projects"
    "/nonexistent/path"
    "$HOME/Documents"
)

for dir in "${directories[@]}"; do
    check_directory "$dir"
done

The double bracket syntax [[ ]] is more robust than single brackets and handles special characters better.

Combining Existence with Permission Checks

Often you need to verify not just that a directory exists, but that you can actually use it:

#!/bin/bash

validate_directory() {
    local dir_path="$1"
    local operation="$2"  # read, write, or execute

    # First check if directory exists
    if [[ ! -d "$dir_path" ]]; then
        echo "Error: Directory does not exist: $dir_path"
        return 1
    fi

    # Check specific permissions
    case "$operation" in
        "read")
            if [[ -r "$dir_path" ]]; then
                echo "✓ Directory is readable: $dir_path"
                return 0
            else
                echo "✗ Directory is not readable: $dir_path"
                return 1
            fi
            ;;
        "write")
            if [[ -w "$dir_path" ]]; then
                echo "✓ Directory is writable: $dir_path"
                return 0
            else
                echo "✗ Directory is not writable: $dir_path"
                return 1
            fi
            ;;
        "execute")
            if [[ -x "$dir_path" ]]; then
                echo "✓ Directory is executable: $dir_path"
                return 0
            else
                echo "✗ Directory is not executable: $dir_path"
                return 1
            fi
            ;;
        *)
            echo "Error: Invalid operation. Use 'read', 'write', or 'execute'"
            return 1
            ;;
    esac
}

# Example usage
project_dir="/home/user/my_project"
validate_directory "$project_dir" "read"
validate_directory "$project_dir" "write"

This approach ensures your script can actually perform the intended operations on the directory.

Creating Directories with Existence Checks

A common pattern is checking for directory existence and creating it if necessary:

#!/bin/bash

ensure_directory() {
    local dir_path="$1"
    local permissions="$2"  # optional, defaults to 755

    # Set default permissions if not provided
    permissions="${permissions:-755}"

    if [[ -d "$dir_path" ]]; then
        echo "Directory already exists: $dir_path"
        return 0
    else
        echo "Creating directory: $dir_path"
        if mkdir -p "$dir_path"; then
            chmod "$permissions" "$dir_path"
            echo "✓ Directory created successfully: $dir_path"
            return 0
        else
            echo "✗ Failed to create directory: $dir_path"
            return 1
        fi
    fi
}

# Example: Setting up project directory structure
project_root="/home/user/new_project"
directories=(
    "$project_root"
    "$project_root/src"
    "$project_root/tests"
    "$project_root/docs"
    "$project_root/logs"
)

for dir in "${directories[@]}"; do
    ensure_directory "$dir" "755"
done

# Create a restricted directory
ensure_directory "$project_root/private" "700"

The mkdir -p command creates parent directories as needed, making this approach very reliable.

Handling Edge Cases and Special Paths

Real-world directory checking needs to handle various edge cases:

#!/bin/bash

robust_directory_check() {
    local dir_path="$1"

    # Handle empty input
    if [[ -z "$dir_path" ]]; then
        echo "Error: No directory path provided"
        return 1
    fi

    # Expand tilde and variables
    dir_path="${dir_path/#\~/$HOME}"
    dir_path=$(eval echo "$dir_path")

    # Check if path exists and is actually a directory
    if [[ -e "$dir_path" ]]; then
        if [[ -d "$dir_path" ]]; then
            echo "✓ Valid directory: $dir_path"
            return 0
        else
            echo "✗ Path exists but is not a directory: $dir_path"
            return 2
        fi
    else
        echo "✗ Path does not exist: $dir_path"
        return 1
    fi
}

# Test with various path formats
test_paths=(
    "~/Documents"
    '$HOME/Desktop'
    "/tmp"
    "/tmp/some_file.txt"  # This might be a file, not directory
    ""  # Empty path
    "/nonexistent/path"
)

for path in "${test_paths[@]}"; do
    echo "Testing: '$path'"
    robust_directory_check "$path"
    echo "Return code: $?"
    echo "---"
done

This function handles variable expansion, empty inputs, and distinguishes between non-existent paths and paths that exist but aren't directories.

Practical Application: Backup Script

Here's a real-world example that demonstrates directory checking in a backup script:

#!/bin/bash

create_backup() {
    local source_dir="$1"
    local backup_root="$2"

    # Validate source directory
    if [[ ! -d "$source_dir" ]]; then
        echo "Error: Source directory does not exist: $source_dir"
        return 1
    fi

    if [[ ! -r "$source_dir" ]]; then
        echo "Error: Cannot read source directory: $source_dir"
        return 1
    fi

    # Create backup directory structure
    backup_date=$(date +"%Y-%m-%d_%H-%M-%S")
    backup_dir="$backup_root/backup_$backup_date"

    # Ensure backup root exists
    if [[ ! -d "$backup_root" ]]; then
        echo "Creating backup root directory: $backup_root"
        mkdir -p "$backup_root" || {
            echo "Error: Failed to create backup root: $backup_root"
            return 1
        }
    fi

    # Check if backup root is writable
    if [[ ! -w "$backup_root" ]]; then
        echo "Error: Backup root is not writable: $backup_root"
        return 1
    fi

    # Create specific backup directory
    if mkdir "$backup_dir"; then
        echo "Created backup directory: $backup_dir"
    else
        echo "Error: Failed to create backup directory: $backup_dir"
        return 1
    fi

    # Perform the backup
    echo "Starting backup from $source_dir to $backup_dir"
    if cp -r "$source_dir"/* "$backup_dir"/; then
        echo "✓ Backup completed successfully"
        echo "Backup location: $backup_dir"
        return 0
    else
        echo "✗ Backup failed"
        return 1
    fi
}

# Example usage
source_directory="$HOME/important_docs"
backup_location="/backups/documents"

create_backup "$source_directory" "$backup_location"

This backup script demonstrates comprehensive directory validation before performing file operations.

Directory Checking in Loops and Conditionals

When processing multiple directories, combine existence checks with other operations:

#!/bin/bash

process_project_directories() {
    local base_dir="$1"

    # Check if base directory exists
    if [[ ! -d "$base_dir" ]]; then
        echo "Base directory not found: $base_dir"
        return 1
    fi

    # Process each subdirectory
    for project in "$base_dir"/*; do
        # Check if it's actually a directory
        if [[ -d "$project" ]]; then
            project_name=$(basename "$project")
            echo "Processing project: $project_name"

            # Check for specific subdirectories
            if [[ -d "$project/src" ]]; then
                echo "  ✓ Source directory found"
                src_files=$(find "$project/src" -name "*.py" | wc -l)
                echo "  Python files: $src_files"
            else
                echo "  ✗ No source directory"
            fi

            if [[ -d "$project/tests" ]]; then
                echo "  ✓ Tests directory found"
            else
                echo "  ⚠ No tests directory"
            fi

            # Check if logs directory exists, create if needed
            if [[ ! -d "$project/logs" ]]; then
                mkdir "$project/logs"
                echo "  Created logs directory"
            fi

            echo "---"
        fi
    done
}

# Process all projects in workspace
workspace="/home/user/workspace"
process_project_directories "$workspace"

This example shows how to combine directory existence checks with file processing and directory creation.

Using Functions for Reusable Directory Operations

Create utility functions that you can reuse across multiple scripts:

#!/bin/bash

# Utility functions for directory operations

dir_exists() {
    [[ -d "$1" ]]
}

dir_is_readable() {
    [[ -d "$1" && -r "$1" ]]
}

dir_is_writable() {
    [[ -d "$1" && -w "$1" ]]
}

dir_is_empty() {
    [[ -d "$1" && -z "$(ls -A "$1")" ]]
}

ensure_dir() {
    local dir_path="$1"
    local permissions="${2:-755}"

    if ! dir_exists "$dir_path"; then
        mkdir -p "$dir_path" && chmod "$permissions" "$dir_path"
    fi
}

# Example usage in a deployment script
app_name="myapp"
app_dirs=(
    "/var/log/$app_name"
    "/var/run/$app_name"
    "/etc/$app_name"
    "/opt/$app_name"
)

echo "Setting up application directories for $app_name"

for dir in "${app_dirs[@]}"; do
    if ensure_dir "$dir"; then
        echo "✓ Directory ready: $dir"
    else
        echo "✗ Failed to setup: $dir"
        exit 1
    fi
done

echo "All directories configured successfully"

These utility functions make your scripts more readable and maintainable.

Next Steps

You now have multiple approaches for checking directory existence in Bash scripts. Consider exploring advanced file system operations like monitoring directory changes with inotify, implementing recursive directory processing, or building more sophisticated backup and synchronization scripts. You might also want to look into using these techniques with configuration management tools or deployment automation.

Good luck with your directory management scripts!

Published: 2024-05-20|Last updated: 2024-05-20T14:30:00Z

Found an issue?