I have directory that has a number of sub-directories and would like to find any duplicates. The folder structure looks something like this:

└── Top_Dir
    └── Level_1_Dir
        ├── standard_cat
        │   ├── files.txt
        ├── standard_dog
        │   └── files.txt
        └── standard_snake
            └── files.txt
    └── Level_2_Dir
        ├── standard_moon
        │   ├── files.txt
        ├── standard_sun
        │   └── files.txt
        └── standard_cat
            └── files.txt
    └── Level_3_Dir
        ├── standard_man
        │   ├── files.txt
        ├── standard_woman
        │   └── files.txt
        └── standard_moon
            └── files.txt

With the above example I would like to see an output of:


I have been doing some searching on how to get this done via bash and I got nothing. Anyone know a way to do this?


3 Answers 3


I had the same problem with my music collection... most tools/scripts were noisy (listing filenames) or did checksums of file contents, which is far too slow...

Special characters, spaces, and symbols made this challenging... the strategy is to MD5sum the sorted file names along with the parent directory, then the script can sort hashes to find duplicates. We must sort children file names, as find does not guarantee file order in two different directories.

Bash Script (Debian 10):


# usage: ./find_duplicates tunes_dir
# output: c547c3bcf85b9c578a1a52dd20665343 - /mnt/tunes/soul brothers/Motherlode
# MD5 is generated from all children filenames + album folder name
# sort list by MD5 then list duplicate (32bit hashes) representing albums
# Album/CD1/... Album/CD2/... will show (3) results if Album is duplicated
# CD1/2 example is indistinguishable from Discography/Album/Song.mp3

if [ $# -eq 0 ]; then
    echo "Please supply tunes directory as first arg"
    exit 1

# Using absolute path of tunes_dir param
find $(readlink -f $1) -type d | while IFS= read -r line
    cd "$line"
    children=$(find ./ -type f | sort)
    base=$(basename "$line")
    sum=$(echo $children $base | md5sum)
    echo $sum $line
done | sort -n | uniq -D -w 32

Directory structure:

user@pc:~/test# find . -type d
./super soul brothers
./super soul brothers/Stritch's Brew
./super soul brothers/Fireball!
./super soul brothers/Motherlode

Example output:

user@pc:~# ./find_duplicates  test/
07b0f79429663685f4005486af20247a - /root/test/car_tunes/Fireball!
07b0f79429663685f4005486af20247a - /root/test/super soul brothers/Fireball!
  • Thanks for this script. You're a life-saver.
    – telometto
    Commented Aug 13, 2022 at 15:39

Using bash release 4 or newer. On macOS, this may be installed via the Homebrew package manager, as the default bash is too old.

# Make glob patterns disappear rather than remain unexpanded
# if the don't match (nullglob).
# Make glob patterns also match hidden names (dotglob).
shopt -s nullglob dotglob

# Create an associative array that hold the number of times
# a directory's name has been seen (the basename of the directory's
# pathname is the key into this array).
declare -A count

# Set the positional parameters ($1, $2, etc.) to the pathnames
# of the directories that we're interested in.
set -- Top_Dir/*/*/

# Loop over out directory paths,
# and count how many times each basename occurs.
for dirpath do
        name=$( basename "$dirpath" )
        count["$name"]=$(( count["$name"] + 1 ))

# Loop over the directory paths again, but this time
# output each directory whose basename occurs more than once.
for dirpath do
        name=$( basename "$dirpath" )
        [[ ${count["$name"]} -gt 1 ]] && printf '%s\n' "$dirpath"


$ tree -F
|-- Top_Dir/
|   |-- Level_1_Dir/
|   |   |-- standard_cat/
|   |   |-- standard_dog/
|   |   `-- standard_snake/
|   |-- Level_2_Dir/
|   |   |-- standard_cat/
|   |   |-- standard_moon/
|   |   `-- standard_sun/
|   `-- Level_3_Dir/
|       |-- standard_man/
|       |-- standard_moon/
|       `-- standard_woman/
`-- script

13 directories, 1 file
$ bash script

To support older bash releases, you may choose to store the unique basenames of the directories and the counts of how many times each basename has been seen in two separate ordinary arrays. This requires a linear search in each loop:

shopt -s nullglob dotglob

set -- Top_Dir/*/*/

for dirpath do
        name=$( basename "$dirpath" )

        for i in "${!names[@]}"; do
                if [[ ${names[i]} == "$name" ]]; then

        if "$found"; then
                counts[i]=$(( counts[i] + 1 ))
                names+=( "$name" )
                counts+=( 1 )

for dirpath do
        name=$( basename "$dirpath" )

        for i in "${!names[@]}"; do
                if [[ ${names[i]} == "$name" ]]; then
                        [[ ${counts[i]} -gt 1 ]] && printf '%s\n' "$dirpath"

This worked using bash on Ubuntu. It only matches duplicate directories irrespective of depth in the tree. The portion within the $() builds a list of pipe-separated directory names by counting duplicates in the last column of ls -l. This pipe-separated list is filtered using grep over the list of all directories. Also, not accounting for other files (didn't use whole word matches etc.)

> ls -lR Top_Dir/ | grep -E $(ls -lR Top_Dir/ | grep ^d | rev | cut -d" " -f1 | rev | sort | uniq -d | head -c -1 | tr '\n' '|') | grep -v ^d | sed 's/://'





  • First, thanks for your reply. When running the command I am getting a head: illegal byte count -- -1 error. Any thoughts on that? I have been paying with the head command but not seeing what is wrong with it.
    – dino
    Commented Jun 10, 2016 at 0:52
  • It's probably specific to your version of head. The link also contains other solutions to truncate the last line.
    – Jedi
    Commented Jun 10, 2016 at 0:59
  • 1
    did not function for special characters / spaces in folder names
    – Kevin
    Commented Dec 15, 2020 at 18:55
  • This explicitly disqualifies filenames with spaces, and also filenames that contain special characters, such as tabs, newlines, or any non-printable character (ls -l would show these as ?). Also note that the user is on macOS, which does not generally have GNU tools installed.
    – Kusalananda
    Commented Mar 3, 2021 at 20:36

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .