Tell me more ×
Unix & Linux Stack Exchange is a question and answer site for users of Linux, FreeBSD and other Un*x-like operating systems.. It's 100% free, no registration required.

I'm having trouble with a bash script I want to make

I know ls will list files that are in a directory but I want it to list directories that are in directory1 but NOT in directory2, and then list files in directory2 that are NOT in directory1.

In a feeble attempt, i tried:

ls -al | diff directory1 directory2

Quickly I realized why it didn't work. Can anyone help out a total bash nerd out? Thanks!

share|improve this question

5 Answers

Given bash, this might be easiest as

$ comm <(ls -a dir1) <(ls -a dir2)

The <(command) expression runs command over a pipe and substitutes a /dev/fd reference:

mress:10018 Z$ echo <(ls)
/dev/fd/11

So the command above runs ls -a on each directory and feeds their outputs as file arguments to comm, which outputs up to 3 columns, tab-indented: entries only in the first, entries in both, entries in the second. (That is, if it's in both then it is indented by a tab, if it's only in the second it's indented by 2 tabs.) You can also suppress columns by number: comm -1 foo bar displays only the lines in both and lines in the second file, the latter indented by one tab. (This is most commonly used by suppressing all but the column you want: comm -13 foo bar shows only the lines in common.)

Given that you want those in the first directory, that translates to

$ comm -23 <(ls -a dir1) <(ls -a dir2)

If you need more than just whether it is present, use diff -r, which will output diffs for the files in common and a one-line message for files found only in one or the other.

share|improve this answer
1  
ls will come with all problems with white space, tabs, linefeeds, backspaces and such in filenames. – user unknown Mar 11 '11 at 0:54
The only one that will confound this is newlines, and I'd argue you have problems anyway in that case. :) If you're paranoid, use ls -b. – geekosaur Mar 11 '11 at 0:55
Not with find, do I? – user unknown Mar 11 '11 at 1:06
Depends on what you're invoking with find. But my main complaint about find is that it's a really heavy sledgehammer to swat what is usually a rather small fly. – geekosaur Mar 11 '11 at 1:08
Thanks! That works but can I get it to output the contents that are in file b that are NOT in file a without using 'comm'? – soju Mar 11 '11 at 1:19
show 15 more comments

And here a pure script. These are the directories a and b:

find a b
a
a/a
a/b
a/c
a/f
a/f/h
a/f/i
b
b/b
b/c
b/d
b/f
b/f/g
b/f/h

Here is the command:

cd a
find ./ -exec test ! -e ../b/{} ";" -print 

output:

./a
./f/i

Swap a and b for files in a but not in b. The ! is a negation. -e tests for -existance. In prosa: "Test if not exists the file found in a in ../b".

Note: You have to dive into a first, to get names without 'a'. For the second comparison you have to cd ../b.

share|improve this answer

If you prefer a graphical tool, use

xxdiff dir1 dir2 

you might need to install it first. Similar programs are

gtkdiff
tkdiff

The midnight commander has a compare directories command build in, which works nice, if you don't go for subdirs.

share|improve this answer
1  
No, I am not looking for a graphical tool, thank though! – soju Mar 11 '11 at 0:26

You could use the neglected join command. Here some setup for two example directories, d1/ and d2/, each of which has some files with names unique to the directory, and some files with names in common with the other directory. This is only an example, so I used single-letter file names to illustrate file names unique to one or the other, and file names in common.

# set up for example
mkdir d1 d2
for name in a  b  c  d  e  f  g  h
do
    touch d1/$name
done
for name in e f g h i j k l
do
    touch d2/$name
done

ls -1 d1 > d1.out   # That's "minus one" not "minus ell"
ls -1 d2 > d2.out
join d1.out d2.out       # files common to both d1/ and d2/
join -v 1 d1.out d2.out  # files only in directory d1/
join -v 2 d1.out d2.out  # files only in directory d2/

For me, it shows files like this:

 5:51PM 100 % join d1.out d2.out
e
f
g
h
 5:51PM 101 % join -v 1 d1.out d2.out
a
b
c
d
 5:52PM 102 % join -v 2 d1.out d2.out
i
j
k
l

UPDATE: You'd want to do different things in real life to accomodate files with whitespace in them, as join uses the first field "delimited by whitespace" to decide which lines are uniqe and which lines are common.

share|improve this answer
You should test your program with a file called "d2/f ". Rule of thumb: Nearly never use ls in scripts. – user unknown Mar 11 '11 at 1:13
Can you clarify a bit? Should d2/ only have one file, d2/f? Because I tried this, and it works as expected. – Bruce Ediger Mar 11 '11 at 2:11
I think he is concerned about filenames that include spaces (or tabs, since both are default input field delimiters for join). Maybe join -t '' (no delimiter) would help that case. – Chris Johnsen Mar 11 '11 at 4:59
Yes, it isn't d2/f but d2/f . Tabs and newlines in filenames are rare, but allowed. – user unknown Mar 11 '11 at 19:59

You can use find and awk to solve this.

With following layout:

$ mkdir a b a/1 b/1 b/2 a/3
$ touch a/f1 b/f1 a/f2 b/f3

Part one:

$ find a b -mindepth 1 -maxdepth 1 -type d | \
    awk -F/ ' { if (!w[$1]) w[$1]=++i; if (w[$1]>1) b[$2]=1; else a[$2]=1; }
          END { for (x in a) if (!b[x]) print x }'
3

Part two:

$ find b a -mindepth 1 -maxdepth 1 -type f | \
    awk -F/ ' { if (!w[$1]) w[$1]=++i; if (w[$1]>1) b[$2]=1; else a[$2]=1; }
          END { for (x in a) if (!b[x]) print x }'
f3

This compares to a comm solution:

$ comm -23 <(ls a) <(ls b)    
3
f2
$ comm -13 <(ls a) <(ls b)
2
f3

And to a join solution:

$ join -v1 <(ls a) <(ls b)
3
f2
$ join -v2 <(ls a) <(ls b)
2
f3
share|improve this answer

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

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