2

I'm using bash shell. I want to see a diff of file names in two directories, but only of direct children of those directories. So for instance, if I have these directories with child files/dirs named ...

dir1
-> abc
-> def
-> ghi

dir2
-> abc
-> fff
-> ghi

I would like to be able to run some command that would tell me that dir2 has a file/dir named "fff" that dir1 doesn't have, whereas "dir1" has a child file/dir named "def" that dir2 doesn't have. How do I do that?

1
for name in dir1/* dir2/*; do
    bname=${name##*/}

    if [ ! -e "dir1/$bname" ]; then
        printf '"%s" not in dir1\n' "$bname"
    elif [ ! -e "dir2/$bname" ]; then
        printf '"%s" not in dir2\n' "$bname"
    fi
done

This will iterate over all names in dir1 and dir2. $bname will be the basename of the name ($name without path).

If the (base)name can not be found in dir1, then this is reported. Otherwise, if it's not found in dir2 it's reported.

This copes with filenames containing embedded newlines and should run with /bin/sh or any compatible sh shell.

This could potentially be expanded to more than two directories. With bash:

dirs=( dir1 dir2 dir3 dir4 )

for dir in "${dirs[@]}"; do
    for name in "$dir"/*; do

        bname=${name##*/}

        for tdir in "${dirs[@]}"; do
            if [ ! -e "$tdir/$bname" ]; then
                printf '"%s" not found in %s\n' "$bname" "$tdir"
            fi
        done

    done
done

$ tree
.
|-- dir1
|   `-- filename 1
|-- dir2
|   |-- filename 1
|   |-- filename 3
|   `-- hello
world
`-- script.sh

$ sh script.sh
"filename 3" not in dir1
"hello
world" not in dir1
0

For this I quite like comm -3, on plain ls’s output:

$ comm -3 <(ls dir1) <(ls dir2)
def
        fff

Entries in the first column are only in the first directory, entries in the second column are only in the second directory; the -3 option removes the third column which displays entries common to both. You can also specify -1 to only see files present in dir2 but not in dir1, and -2 to only see files present in dir1 but not in dir2.

For this to work you need to ensure you don’t have any options set for ls, either via its OPTIONS configuration or an alias. You can avoid such issues using a variant such as

$ comm -z -3 <(cd dir1; printf "%s\0" *) <(cd dir2; printf "%s\0" *) | tr '\0' '\n'
def
        fff

The latter avoids issues with special characters in filenames. You can combine it with -1 or -2 and drop the tr to feed it into xargs -0 for further processing of either set of files too.

  • ls is problematic, f.i. on my Linux machines I have OPTIONS="-b -T 0 --quoting-style=shell". With this a file named a b is listed as 'a b'. – Satō Katsura Sep 19 '17 at 19:02
  • Hey this doens't work. It is listing files taht are in both directories and that isn't what I want. – Dave Sep 19 '17 at 19:07
  • @Dave see my update, you can change comm’s options to see exactly what you want. – Stephen Kitt Sep 19 '17 at 20:34
  • @SatōKatsura that can be avoided, see my update. – Stephen Kitt Sep 19 '17 at 20:39

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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