Take the 2-minute tour ×
Ask Ubuntu is a question and answer site for Ubuntu users and developers. It's 100% free, no registration required.

Let's say I have a source/ directory which has many subdirectories. Some of these subdirectories have sub-subdirectories, and some of those sub-subdirectories have sub-sub-subdirectories, and so on.

What I want to do is recursively find all lowest-level subdirectories (subdirectories that do not have any further subdirectories) of source/ and move them to a new target directory.

For instance, let's say the original source/ looks like this:

/source/dir1               --Subdirectory without sub-subdirectory
/source/dir2               --Subdirectory with sub-subdirectory
/source/dir2/dir2.a        --Sub-subdirectory without sub-sub-subdirectory
/source/dir2/dir2.b        --Sub-subdirectory without sub-sub-subdirectory

Then after the operation, the target directory should look like this:

/target/dir1
/target/dir2.a
/target/dir2.b

Is there a way to do this with a terminal command?

share|improve this question

2 Answers 2

up vote 1 down vote accepted

Assuming you are on a file-system where leaf directories have exactly 2 hard links, you can use a combination of find, xargs and mv like so:

$ find . -type d -printf "%n %p\n"                   \
    | grep '^2 '                                     \
    | perl -ne 'chomp; s/[0-9]+\s+//; print "$_\0";' \
    | xargs -0 -I'{}' mv -v '{}' /target/

This uses find to find only directories and print the number of hard links those directories have.

Then we use grep to remove any that aren't "leaves".

Next we use Perl to chop off the hard-link count, and print the files separated by \0.

Finally that lets us use xargs with the -0 flag to move the found directories into the target directory.

Using nulls (\0) rather than newlines or spaces is important for files with spaces in them. If your directory names are guaranteed not to have spaces in them you might be able to simplify slightly.

$ find . -type d -printf "%n %p\n" | grep '2 ' | cut -d " " -f 2 | xargs -I'{}' mv -v '{}' /target/

But it's probably better to be safe than sorry.

share|improve this answer

One approach might be to use find to get each directory, and pass the results to a second find, and perform the move only if the second find does to return any directories below the given one e.g.

while read -rd $'\0' dir; do 
    [[ -z $(find "$dir" -mindepth 1 -type d) ]] && mv -t path/to/target "$dir"
done < <(find -depth -type d -print0)

or

find -depth -type d -execdir \
bash -c '[[ -z $(find "$0" -mindepth 1 -type d) ]] && mv -t path/to/target "$0"' {} +

e.g. given

$ tree .
.
├── dir1
└── dir2
    ├── dir2.a
    └── dir2.b

then

$ while read -rd $'\0' dir; do [[ -z $(find "$dir" -mindepth 1 -type d) ]] && echo mv -t path/to/target "$dir"; done < <(find -depth -type d -print0)
mv -t path/to/target ./dir1
mv -t path/to/target ./dir2/dir2.b
mv -t path/to/target ./dir2/dir2.a
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.