1

I have a large n-level directory structured as follows:

root
  |
   subdir1
      |
       sub_subdir1
           |
            ....(n-2 levels).....
                                 |
                                  file1
  |
   subdir2
      |
       sub_subdir2
           |
            ....(n-2 levels).....
                                 |
                                  file2

I want to flatten the directory so that all level 1 subdirs contain files. I also want to remove the level 2 to (n-1) sub_subdirs as they contain no files. Please note that the subdirs all have different names.

Desired Result

root
  |
   subdir1
      |
       file1
  |
   subdir2
      |
       file2

I have found a lot of posts explaining methods to flatten directories but none that explains how to do this in a controlled manner, i.e.,

  • by specifying the levels to be flattened
  • or doing it recursively for all sub_directories in a root directory
sheth7
  • 201
  • Did you find any solutions to flatten a directory structure, moving all the files to the top level and keeping no subdirectories? If so, just apply that solution in each top-level subdirectory. – Scott - Слава Україні Jul 24 '19 at 18:04
  • True. This can be done. However, I am just looking for a more functional solution. Thanks tho ! – sheth7 Jul 25 '19 at 16:13

2 Answers2

2

Using zsh:

cd /root
for subdir in subdir*
do
  mv "$subdir"/**/*(.) "$subdir"
  rm -r "$subdir"/*(/)
done

This:

  1. changes to the "/root" directory (from your example)
  2. loops over every subdirectory named subdir* (again from your example: matching subdir1 and subdir1)
  3. moves the (expected one, but would match all) matching file(s) under that subdirectory into that subdirectory. This uses zsh's ** recursive globbing feature, limited then by the glob qualifier *(.) which says: any entry in this directory that's a plain file
  4. after moving the file, recursively remove every subdirectory under that subdirectory; this again uses a zsh glob qualifier *(/) which says to match entries that are directories.
Jeff Schaller
  • 67,283
  • 35
  • 116
  • 255
1

In bash, do the following:

shopt -s dotglob
for d in root/*/
do
        find "$d" -type f -exec mv -i -t "$d" {} +
        find "$d" -mindepth 1 -type d -delete
done

This looks at everything in “root”, including dot-files.  If you don’t need to look at dot-files, leave out the shopt -s dotglob.  Warning: do not add .* or root/.* to the for command.

The / at the end of the glob (root/*/), as suggested by Jeff, gets us only the subdirectories, so we don’t need to test each one (e.g., with if).

For every subdirectory of root, find all files in and under that subdirectory and move them into the subdirectory.  Use -i to prevent overwriting files (e.g., if you have multiple files called file1 distributed under subdir1).  -i means “interactive”, and it will ask you whether it should overwrite an existing file.  Be aware that it might refuse to do so, even if you say “yes”.  If your version of mv doesn’t support the -t option, replace the line with the mv command with

        find "$d" -type f -exec mv -i {} "$d" ';'

Then delete all the subdirectories under the top-level subdir.  Note that this (find … -delete) will fail if any files didn’t get moved.

This is partially inspired by derobert’s answer to Flattening a nested directory.