29

Right now, this is what my code looks like:

#!/bin/bash

Dir1=$1
Dir2=$2

for file1 in $Dir1/*; do
    for file2 in $Dir2/*; do
        if [[ $file1 == $file2 ]]; then
            echo "$file1 is contained in both directories"
        fi
    done
done

I am trying to compare the file names of the two directories entered and say that the file is in both directories if the filename matches. When I try to run it though, nothing is echo-ed even though I have the same file in both directories.

5 Answers 5

61

Files that are in both Dir1 and Dir2:

find "$Dir1/" "$Dir2/" -printf '%P\n' | sort | uniq -d

Files that are in Dir1 but not in Dir2:

find "$Dir1/" "$Dir2/" "$Dir2/" -printf '%P\n' | sort | uniq -u

Files that are in Dir2 but not in Dir1:

find "$Dir1/" "$Dir1/" "$Dir2/" -printf '%P\n' | sort | uniq -u
4
  • 3
    this is very elegant solution on account of the simple logic of printing all files in one of the directories twice, then sorting them, so that they are filtered by uniq later. could not find the documentation on %P for printf, would be helpful if you could share a link?
    – somshivam
    Commented Oct 7, 2015 at 16:45
  • @AnirtakVarma, thank you. Sorry didn't see you comment until now. Man page for find covers it under -printf section. It says: %P File's name with the name of the command line argument under which it was found removed Commented Aug 1, 2016 at 16:44
  • I had a special case where I wanted to compare Dir1 and Dir2, but Dir2 is a subfolder of Dir1. In that case I needed to exclude the output of Dir2 in Dir1. This is what I used to find Files that are in Dir1 but not in Dir2: (find . -printf '%P\n' | grep -v Dir2 && find Dir2/ -printf '%P\n' && find Dir2/ -printf '%P\n') | sort | uniq -u It assumes I am already inside Dir1.
    – Christoph
    Commented Sep 24, 2017 at 12:43
  • 1
    Really cool way, allows to find files with ignoring paths. Just need to use %f for printf instead of %P.
    – Maxim
    Commented Nov 5, 2023 at 6:42
6

If you want to know what's common to two directories then this is another way with much less coding.

#!/bin/bash

comm -12 <(ls -F $1) <(ls -F $2)

See man comm for more information about the comm utility.

5
  • 1
    I was going to post similar answer with find instead of ls. But this approach should also work if there are no sub-directories which need to be compared. For further fine tuning, you may consider adding -F flag to ls..
    – anishsane
    Commented Jan 29, 2015 at 3:30
  • This approach fails (or at least is unclear) if filenames contain newlines. Try running touch red blue red$'\n'white in two directories and comparing them.
    – ghoti
    Commented Jan 29, 2015 at 3:58
  • @ghoti, I just tested with your touch command and it still worked with a file name containing a newline, so I'm not sure why you say this approach fails. Did you actually test it yourself? Commented Jan 29, 2015 at 4:12
  • @anishsane, yes using find has its advantages however since the OP had no mention or coding of recursion I chose to keep it simple. Commented Jan 29, 2015 at 4:14
  • @ghoti's comment is correct. parsing ls (or find for that matter) is discouraged.
    – anishsane
    Commented Jan 29, 2015 at 4:18
3

It doesn't work because you're comparing variables that contain the directory prefix. Just remove the prefix before comparing:

name1=${file1##*/}
name2=${file2##*/}
if [[ $name1 == $name2 ]]; then
    echo "$name1 exists in both directories"
fi

Also, nested loops seems like an inefficient way to do this. You just need to get the filenames from one directory, and use a simple file existence check for the other directory.

for file in $Dir1/*; do
    name=${file##*/}
    if [[ -f $Dir2/$name ]]; then
        echo "$name exists in both directories"
    fi
done
1

I just tested this and it worked:

DIR1=$(ls dir1)
DIR2=$(ls dir2)

for i in $DIR1; do
    for j in $DIR2; do
        if [[ $i == $j ]]; then
            echo "$i == $j"
        fi
    done
done
1
  • Fails on filenames with special characters. Please see ParsingLs for a detailed explanation.
    – ghoti
    Commented Jan 29, 2015 at 3:48
0

Your comparison fails because Dir1/foo is not the same as Dir2/foo. Instead, if you change to one directory, your * will expand to just the filenames:

#!/bin/bash

Dir1="$1"
Dir2="$2"

if ! cd "$Dir1"; then
  echo "ERROR: Couldn't find directory $Dir1" >&2
  exit 1
fi

if [[ ! "$Dir2" =~ ^/ ]]; then
  echo "ERROR: Second directory must be a full path." >&2
  exit 1
fi

for file1 in *; do
    if [ -f "$Dir2/$file1" ]; then
        echo "$file1 is contained in both directories"
    fi
done

Note that this only matches file names. If you want to make sure it's really the same file, you should use cmp to compare them.

Not the answer you're looking for? Browse other questions tagged or ask your own question.