How to "squeeze" directory levels

Viewed 96

I have data files buried in deep directory structures for a lot of IDs like:

ID1/a/b/c/data1
ID1/d/e/data2
ID1/f/g/h/i/data3

ID2/j/data1
ID2/k/l/m/data2

ID3/m/n/data1
ID3/o/data2
ID3/p/q/r/s/t/u/data3
ID3/v/w/data4
ID3/x/y/z/data5
...

Is there a convenient way to "squeeze" those directories so that data files are put immediately under each IDs? Like:

> ls ID1
data1 data2 data3

> ls ID2
data1 data2

> ls ID3
data1 data2 data3 data4 data5
3 Answers

You could use a pattern similar to this:

find ID1/*/ -type f -exec mv -t ID1 {} +

ID1/*/ — look in all directories of ID1

-type f — look only for files

-exec mv -t ID1 — invoke mv on found files and move them to the ID1 directory

{} + — find will replace these args with the names of the files when it invokes mv


In a loop to cover all directories:

for dir in ID*/ ; do
    find $dir/*/ -type f -exec mv -t $dir {} +
done

Note: whitespace in directory names will require a different solution.

Here's a possible pattern for systems that do not support the mv -t option:

find ID1/*/ -type f -print0 | xargs -0 -I{} mv {} ID1

-type f — look only for files

-print0 — output list of files to stdout separated with NUL characters

The xargs utility will receive the output from find on its stdin. The xargs options are:

-0 — accept NUL-separted input

-I{} - use the string {} to tell xargs where to replace the command line pattern with actual input (one at a time)

mv {} ID1 — the mv command invoked by xargs where {} will be replaced with the pathname of one file at a time. mv will be invoked as many times as there are files passed from find.


In a loop to cover all directories:

for dir in ID*/ ; do
    find $dir/*/ -type f -print0 | xargs -0 -I{} mv {} $dir
done

Second "find" statement to clean up the empty directories

for i in `ls`
do
  find $i -type f -exec mv {} $i \;
  find $i/* -d -type d -exec rmdir {} \; 
done
Related