Linux/Unix/Mac OSX – Find & Remove Empty Directories

| | | | | | | |

Occasionally you get empty directories and need to clear them out, these scripts below will help with that, just be careful what directory you choose to start the search in.

After years of just having snippets on this page there is now a more complete script, this complete script will default to the directory you run the script in as well as default to list the files that would be deleted rather than just delete.

The original snippets are still below if you just want those as well.

Bash
#!/usr/bin/env bash

###############################################################################
# clean_empty_and_junk.sh
#
# PURPOSE:
#   This script helps clean up a folder by finding or deleting:
#
#     1. Empty files
#        These are files that exist but contain no data.
#
#     2. Empty folders/directories
#        These are folders that do not contain any files or other folders.
#
#     3. Common junk files
#        These are files automatically created by Windows, macOS, sync tools,
#        photo tools, or web design tools. They are usually safe to delete.
#
#     4. Dreamweaver "_notes" folders
#        Adobe Dreamweaver can create "_notes" folders. If you no longer need
#        them, this script can remove them.
#
#
# DEFAULT BEHAVIOR:
#   By default, the script runs in "list" mode.
#
#   That means it will only show what it found.
#   It will NOT delete anything unless you use:
#
#       --delete
#
#
# DEFAULT DIRECTORY:
#   If you do not specify a directory, the script scans the folder where this
#   script itself is stored.
#
#   Example:
#     If this script is saved in:
#
#       /Users/david/Scripts/
#
#     and you run:
#
#       ./clean_empty_and_junk.sh
#
#     then it scans:
#
#       /Users/david/Scripts/
#
#
# SPECIFYING A DIFFERENT DIRECTORY:
#   You can scan a different folder using:
#
#       -d
#
#   or:
#
#       --dir
#
#   Example:
#
#       ./clean_empty_and_junk.sh -d ~/Documents
#
#
# SAFE MODE VS DELETE MODE:
#
#   LIST MODE:
#     This is the default and safest mode.
#     It only prints matching files and folders.
#
#       ./clean_empty_and_junk.sh --list
#
#   DELETE MODE:
#     This mode actually deletes matching files and folders.
#
#       ./clean_empty_and_junk.sh --delete
#
#     Before deleting anything, the script asks for confirmation.
#     You can answer:
#
#       YES
#       yes
#       y
#
#     To cancel, you can answer:
#
#       NO
#       no
#       n
#
#
# EXAMPLES:
#
#   Scan the folder where this script is located:
#
#       ./clean_empty_and_junk.sh
#
#   Scan the folder where this script is located and list matches:
#
#       ./clean_empty_and_junk.sh --list
#
#   Scan the folder where this script is located and delete matches:
#
#       ./clean_empty_and_junk.sh --delete
#
#   Scan your home folder but only list matches:
#
#       ./clean_empty_and_junk.sh -d ~ --list
#
#   Scan your Documents folder and delete matches:
#
#       ./clean_empty_and_junk.sh -d ~/Documents --delete
#
#   Scan a folder with spaces in the name:
#
#       ./clean_empty_and_junk.sh -d "/Volumes/External Drive" --list
#
#
# IMPORTANT WARNING:
#   Be careful when using --delete on large folders such as:
#
#       ~
#       /
#       /Users
#       /Volumes
#
#   Always run --list first so you can review what would be deleted.
#
###############################################################################


###############################################################################
# SAFER BASH SETTINGS
###############################################################################

# This line tells Bash to use stricter error handling.
#
# set -e:
#   Stop the script if a command fails.
#
# set -u:
#   Stop the script if it tries to use a variable that has not been set.
#
# set -o pipefail:
#   If commands are connected together with pipes, fail if any command fails.
#
# These settings help prevent the script from continuing after something goes
# wrong.
set -euo pipefail


###############################################################################
# DEFAULT SETTINGS
###############################################################################

# SCRIPT_DIR stores the folder where this script itself is located.
#
# This makes the script default to scanning its own folder instead of whatever
# folder you happened to be in when you ran the command.
#
# Example:
#
#   Script location:
#     /Users/david/Scripts/clean_empty_and_junk.sh
#
#   SCRIPT_DIR becomes:
#     /Users/david/Scripts
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"


# TARGET_DIR is the folder that will be scanned.
#
# By default, it is set to SCRIPT_DIR, which means the script scans the folder
# where this script is stored.
#
# The user can override this with:
#
#   -d /some/folder
#
# or:
#
#   --dir /some/folder
TARGET_DIR="$SCRIPT_DIR"


# MODE controls whether the script only lists matches or deletes them.
#
# Possible values:
#
#   list
#     Only show matching files and folders.
#
#   delete
#     Actually delete matching files and folders.
#
# The default is "list" because it is safer.
MODE="list"


###############################################################################
# HELP MESSAGE
###############################################################################

# This function prints instructions for how to use the script.
#
# It runs when the user enters:
#
#   -h
#
# or:
#
#   --help
show_help() {
    cat <<EOF
Usage:
  $0 [options]

Options:
  -d, --dir DIRECTORY   Directory to scan.
                        Defaults to the directory this script is in.

  --list                List matching files/directories only.
                        This is the default and does not delete anything.

  --delete              Delete matching files/directories.
                        The script will ask for confirmation first.

  -h, --help            Show this help message.

Examples:
  $0
  $0 --list
  $0 --delete
  $0 -d ~ --list
  $0 -d ~/Documents/WebSite --delete
  $0 -d "/Volumes/External Drive" --list

Recommended safe usage:
  1. First run:
       $0 -d ~/Documents --list

  2. Review the results.

  3. Then run:
       $0 -d ~/Documents --delete
EOF
}


###############################################################################
# READ COMMAND-LINE OPTIONS
###############################################################################

# This section reads any options the user typed after the script name.
#
# For example:
#
#   ./clean_empty_and_junk.sh -d ~/Documents --delete
#
# In that command:
#
#   -d ~/Documents
#     tells the script which folder to scan.
#
#   --delete
#     tells the script to delete matching files/folders instead of only listing.
#
# The while loop continues until all options have been processed.
while [[ $# -gt 0 ]]; do
    case "$1" in

        # -d and --dir both mean:
        #   "Use the next value as the folder to scan."
        #
        # Example:
        #   -d ~/Documents
        #
        # In this example, ~/Documents is stored in TARGET_DIR.
        -d|--dir)
            if [[ $# -lt 2 ]]; then
                echo "ERROR: Missing directory after $1"
                exit 1
            fi

            TARGET_DIR="$2"
            shift 2
            ;;

        # --list means:
        #   "Only show what would be found. Do not delete anything."
        --list)
            MODE="list"
            shift
            ;;

        # --delete means:
        #   "Actually delete matching files and folders."
        #
        # The script still asks for confirmation later before deleting.
        --delete)
            MODE="delete"
            shift
            ;;

        # -h and --help display usage instructions.
        -h|--help)
            show_help
            exit 0
            ;;

        # Anything else is considered an error.
        *)
            echo "ERROR: Unknown option: $1"
            echo
            show_help
            exit 1
            ;;
    esac
done


###############################################################################
# PREPARE AND VERIFY THE TARGET DIRECTORY
###############################################################################

# This expands the ~ shortcut if the user used it.
#
# Example:
#
#   ~/Documents
#
# becomes something like:
#
#   /Users/david/Documents
#
# The eval here is used specifically so that ~ can expand correctly.
TARGET_DIR="$(eval echo "$TARGET_DIR")"


# Check whether the target directory actually exists.
#
# If it does not exist, the script stops.
#
# This prevents the script from running against the wrong place.
if [[ ! -d "$TARGET_DIR" ]]; then
    echo "ERROR: Directory does not exist:"
    echo "  $TARGET_DIR"
    exit 1
fi


# Convert TARGET_DIR into a full absolute path.
#
# Example:
#
#   .
#
# becomes:
#
#   /Users/david/Scripts
#
# This makes the output clearer and avoids confusion.
TARGET_DIR="$(cd "$TARGET_DIR" && pwd)"


###############################################################################
# SHOW CURRENT SETTINGS
###############################################################################

# Print the folder being scanned and the current mode.
#
# This gives the user one more chance to notice if they are scanning the wrong
# folder before anything happens.
echo "Target directory: $TARGET_DIR"
echo "Mode: $MODE"
echo


###############################################################################
# CONFIRM BEFORE DELETING
###############################################################################

# This safety check only runs when the user chose --delete.
#
# In list mode, no confirmation is needed because nothing is being deleted.
if [[ "$MODE" == "delete" ]]; then
    echo "WARNING: This will delete matching files and directories under:"
    echo "  $TARGET_DIR"
    echo

    # Keep asking until the user gives a valid answer.
    while true; do
        read -r -p "Continue? Enter YES/yes/y or NO/no/n: " CONFIRM

        case "$CONFIRM" in

            # These answers mean the user wants to continue.
            YES|yes|Y|y)
                echo "Continuing..."
                break
                ;;

            # These answers mean the user wants to stop.
            NO|no|N|n)
                echo "Cancelled."
                exit 0
                ;;

            # Anything else is not accepted.
            *)
                echo "Please enter YES, yes, y, NO, no, or n."
                ;;
        esac
    done
fi


###############################################################################
# DEFINE COMMON JUNK FILES
###############################################################################

# This list contains file names that are commonly created by operating systems,
# backup tools, sync tools, photo software, or web design tools.
#
# These files are often unnecessary and can prevent folders from being empty.
#
# For example:
#
#   A folder may look empty in Finder or Windows Explorer, but it might still
#   contain a hidden file like .DS_Store or desktop.ini.
#
#   Because of that, the folder is not truly empty yet.
#
# The script removes these junk files before checking for empty directories.
#
# Junk files included:
#
#   Thumbs.db
#     Created by Windows to store thumbnail image previews.
#
#   desktop.ini
#     Created by Windows to store folder display settings.
#
#   error_log
#     Often created by web servers or PHP scripts.
#
#   .DS_Store
#     Created by macOS Finder to store folder view settings.
#
#   ._*
#     AppleDouble metadata files created by macOS, often on external drives,
#     network drives, or non-Mac filesystems.
#
#   SyncToy*.dat
#     Created by Microsoft SyncToy.
#
#   Picasa.ini
#     Created by Google's older Picasa photo software.
#
JUNK_FIND_EXPR=(
    -name "Thumbs.db"
    -o -name "desktop.ini"
    -o -name "error_log"
    -o -name ".DS_Store"
    -o -name "._*"
    -o -name "SyncToy*.dat"
    -o -name "Picasa.ini"
)


###############################################################################
# FIND OR DELETE COMMON JUNK FILES
###############################################################################

echo
echo "Checking junk files..."

# This command searches inside TARGET_DIR and all folders below it.
#
# find "$TARGET_DIR"
#   Search inside the selected folder.
#
# -depth
#   Process files and folders from the deepest level first.
#   This is helpful when deleting because it handles nested folders better.
#
# -type f
#   Only match files, not folders.
#
# \( "${JUNK_FIND_EXPR[@]}" \)
#   Match any of the junk file names listed above.
#
# -print
#   Show the matching file.
#
# -exec rm -fv {} \;
#   Delete the matching file.
#
#   rm:
#     Remove/delete.
#
#   -f:
#     Force removal without asking again for each file.
#
#   -v:
#     Verbose mode, meaning it prints what it removes.
#
#   {}:
#     Placeholder for the file found by find.
#
#   \;:
#     Marks the end of the command being run by find.
if [[ "$MODE" == "list" ]]; then
    find "$TARGET_DIR" -depth -type f \( "${JUNK_FIND_EXPR[@]}" \) -print
else
    find "$TARGET_DIR" -depth -type f \( "${JUNK_FIND_EXPR[@]}" \) -exec rm -fv {} \;
fi


###############################################################################
# FIND OR DELETE DREAMWEAVER "_notes" DIRECTORIES
###############################################################################

echo
echo "Checking Dreamweaver _notes directories..."

# Adobe Dreamweaver can create folders named "_notes".
#
# These may contain site-management metadata.
#
# This section finds or deletes folders named exactly:
#
#   _notes
#
# -type d:
#   Only match directories/folders.
#
# -name "_notes":
#   Match folders named _notes.
#
# In delete mode, rm -rfv is used because the _notes folder may contain files.
#
# rm -r:
#   Remove folders and their contents.
#
# rm -f:
#   Force removal.
#
# rm -v:
#   Print what was removed.
if [[ "$MODE" == "list" ]]; then
    find "$TARGET_DIR" -depth -type d -name "_notes" -print
else
    find "$TARGET_DIR" -depth -type d -name "_notes" -exec rm -rfv {} \;
fi


###############################################################################
# FIND OR DELETE EMPTY FILES
###############################################################################

echo
echo "Checking empty files..."

# This section finds files that are empty.
#
# Empty means the file exists but has a size of 0 bytes.
#
# -type f:
#   Only match files.
#
# -empty:
#   Only match files that are empty.
#
# In list mode, the script only prints the empty files.
#
# In delete mode, it removes them.
if [[ "$MODE" == "list" ]]; then
    find "$TARGET_DIR" -depth -type f -empty -print
else
    find "$TARGET_DIR" -depth -type f -empty -exec rm -fv {} \;
fi


###############################################################################
# FIND OR DELETE EMPTY DIRECTORIES
###############################################################################

echo
echo "Checking empty directories..."

# This section finds folders that are empty.
#
# Empty means the folder contains no files and no subfolders.
#
# This section is intentionally done after removing junk files and empty files.
#
# Why?
#
#   A folder may not be empty at first because it contains files such as:
#
#     .DS_Store
#     desktop.ini
#     Thumbs.db
#
#   After those files are removed, the folder may become empty.
#
# In list mode:
#
#   Matching empty folders are printed.
#
# In delete mode:
#
#   The script uses rmdir instead of rm -rf.
#
# Why rmdir?
#
#   rmdir only removes directories if they are truly empty.
#   This is safer than rm -rf for empty-folder cleanup.
#
# 2>/dev/null:
#
#   Hides harmless error messages.
#
#   Sometimes find may discover a folder, but by the time rmdir runs, another
#   folder removal may have already affected it.
#
# || true:
#
#   Prevents the script from stopping if rmdir cannot remove a directory.
#
#   This is useful because some folders may no longer be empty or may be in use.
if [[ "$MODE" == "list" ]]; then
    find "$TARGET_DIR" -depth -type d -empty -print
else
    find "$TARGET_DIR" -depth -type d -empty -exec rmdir -v {} \; 2>/dev/null || true
fi


###############################################################################
# FINISHED
###############################################################################

echo
echo "Done."

The scripts below are set to ~/ (current user home directory) but can easily be changed to any directory on your system.

This first block will only list the empty files and directories.

Bash
# List empty files
find ~/ -depth  -empty -type f

# List empty directories
find ~/ -depth  -empty -type d

This second block will look in the current user’s home directory and delete the empty files and directories along with some files most people don’t want to have taking up space.

NOTE: You can simply remove line 6 if you only want to remove empty files.

Bash
# Delete Empty Files/Directories - User Home Directory
# Delete empty files
find ~/ -depth -empty -type f -delete

# Remove .DS_Store and more files before delete empty directories so it actually removes empty directories
find ~/ -depth \( -name "Thumbs.db" -o -name "desktop.ini" -o -name "error_log" -o -name ".DS_Store" -o -name "._*" -o -name "SyncToy*.dat" -o -name "Picasa.ini" \) -type f -exec rm -rfv {} \;

# Remove empty files in general before delete empty directories
find ~/ -depth  -empty -type f -exec rm -rfv {} \;

# Delete empty directories
find ~/ -depth  -empty -type d -exec rm -rfv {} \;

This third block will look in the current directory and delete the empty files and directories along with some files most people don’t want to have taking up space.

NOTE: You can simply remove line 6 if you only want to remove empty files.

Bash
# Delete Empty Files/Directories - Current Directory/SubDirectories
# Delete empty files
find . -depth -empty -type f -delete

# Remove .DS_Store and more files before delete empty directories so it actually removes empty directories
find . -depth \( -name "Thumbs.db" -o -name "desktop.ini" -o -name "error_log" -o -name ".DS_Store" -o -name "._*" -o -name "SyncToy*.dat" -o -name "Picasa.ini" \) -type f -exec rm -rfv {} \;

# Remove empty files in general before delete empty directories
find . -depth -empty -type f -exec rm -rfv {} \;

# Delete empty directories
#find . -depth -empty -type d -delete
find . -depth -empty -type d -exec rm -rfv {} \;

Similarly if you have a file or directory in multiple subdirectories that you need removed you can do

Bash
# Delete Files/Directories

# Delete files - Option 1
find ~/ -depth Documents/WebSite -name "Thumbs.db" -type f -exec rm -rfv {} \;

find ~/ -depth Google\ Drive -name "desktop.ini" -type f -exec rm -rfv {} \;

# Delete files - Option 2
find . -depth -name "SyncToy*.dat" -print0 | xargs -0 rm -rf

# Delete directories that Dreamweaver adds
find ~/ -depth -depthDocuments/WebSite -name "_notes" -type d -exec rm -rfv {} \;
Originally Posted on May 4, 2016
Last Updated on June 7, 2026
All information on this site is shared with the intention to help. Before any source code or program is ran on a production (non-development) system it is suggested you test it and fully understand what it is doing not just what it appears it is doing. I accept no responsibility for any damage you may do with this code.