Flatten a Nested Directory & File Hierarchy from Command Line of Mac OS X

Feb 11, 2015 - 16 Comments

Terminal in OS X Have you ever needed to flatten a directory structure, moving all file contents from a directories child folders into a single folder? While you can do this manually by moving around files and folders from the file system of Mac OS X or Linux, a faster option is to turn to the command line. Maybe at one point you created a nested hierarchy of directories that you now need to undo by moving all files out of those nested folders and back into a single directory, or maybe you’re looking to simplify a directory structure, whatever the reason, this trick works quite well.


Using the command line to accomplish flattening of files and directory structures is obviously best reserved for advanced users who are comfortable with using terminal in general, if that doesn’t describe you, consider doing it manually through Finder, or using the Mac Automator app to accomplish similar automation of file system activities. We’re focusing on directory flattening from the command line here, however.

Example of Flattening a Nested File Directory

To better understand what we’re trying to accomplish, let’s take an example imaginary directory structure called TestDirectory located in a user Home folder. In this example, TestDirectory contains subfolders like SubDirectory1, SubDirectory2, SubDirectory3, etc, each with files in those respective folders. What we’re looking to do here is flatten the directory structure, moving all files from SubDirectory(X) to the parent directory “TestDirectory”. The initial directory and contents shown recursively with the could look something like this:

$ find ~/TestDirectory/ -type f
~/TestDirectory/rooty.jpg
~/TestDirectory/SampleDirectory1/beta-tool-preview.jpg
~/TestDirectory/SampleDirectory1/alphabeta-tool.jpg
~/TestDirectory/SampleDirectory2/test-tools.jpg
~/TestDirectory/SampleDirectory3/test-png.jpg
~/TestDirectory/SampleDirectory3/test1.jpg
~/TestDirectory/SampleDirectory3/test2.jpg

To flatten this directory and subdirectory contents out back into the TestDirectory folder, you would use the following command string:

find TargetDirectory/ -mindepth 2 -type f -exec mv -i '{}' TargetDirectory/ ';'

After the directory contents have been flattened, it should look like this when listed out:

~/TestDirectory/rooty.jpg
~/TestDirectory/beta-tool-preview.jpg
~/TestDirectory/alphabeta-tool.jpg
~/TestDirectory/test-tools.jpg
~/TestDirectory/test-png.jpg
~/TestDirectory/test1.jpg
~/TestDirectory/test2.jpg

Note the subdirectories will still exist, they’ll just be empty. Make sense? If not, or if that doesn’t demonstrate what you want to accomplish, you probably don’t want to flatten a directory at all, maybe you’re looking to merge or use ditto to do a complex copy to elsewhere.

Flattening a Directory Structure & Nested File Hierarchy with the Command Line

Ready to proceed? The command string we’re going to use to flatten a directory structure and move all files from subdirectories to the base of the target directory is as follows:

find [DIRECTORY] -mindepth 2 -type f -exec mv -i '{}' [DIRECTORY] ';'

Replace [DIRECTORY] with the directory of your choice to flatten, as demonstrated in the example above.

Yes, the directory appears twice in the command string, the first time is the directory being searched to flatten subdirectories of, and the second time as the destination for the found items.

Be precise with the specified destination, because this is not reversible (well, at least without a lot of manual work on your part), so only do this if you’re absolutely sure you want to relocate all the files in the target directories child directories back to the target root folder.

As mentioned before, you could also do this in the Finder of OS X, or at least observe the file and folder changes in the Finder. Option+clicking the little arrows in the List view opens up all subdirectories, showing the folder hierarchy like this:

Nested directory structure to flatten as shown in the Finder of Mac OS X

After fiddling with a variety of bash and zsh alternatives, this handy trick was left by a commenter on StackExcange and it ended up being the easiest and most compatible method. If you know of a better way to flatten a nested directory, do let us know in the comments!

Enjoy this tip? Subscribe to the OSXDaily newsletter to get more of our great Apple tips, tricks, and important news delivered to your inbox! Enter your email address below:

Related articles:

Posted by: Paul Horowitz in Command Line, Tips & Tricks

16 Comments

» Comments RSS Feed

  1. John Haxby says:

    Replace ‘;’ with ‘+’ and you’ll invoke me to move loads of files at once instead of one at a time. If you’re only renaming a handful of files it won’t make any appreciable difference, but if you’re renaming a few hundred it will.

    Also bear in mind that while directories with tens of thousands of files in them will give you some operational difficulties. This gets really bad if the directory is shared across the network — a simple ‘ls’ can wind up taking several minutes.

    • Peter says:

      Hello, I am really going through some trouble. The command works, but following your advise to replace ‘;’ with ‘+’ in order to move a lot of files at once results in this error: find: -exec: no terminating “;” or “+”

  2. venicejeff says:

    for the average user who might perform this (and doesn’t have the mv command syntax memorized) there’s a simpler way:

    -Instead just open top folder twice in 2 separate browser windows.
    -in one, search for ‘jpg’ or ‘.’ (this might exclude files with no extensions so it’s not perfect), select all, and then drag into the other window or new folder.
    -done.

    also, not sure how the command line will handle items with the same name writing over each other.

  3. Amer Neely says:

    As a programmer I spend half my time in Terminal, so these kinds of tips are always welcome to me. I tried this one but threw a wrench into the works. I started with a directory on the Desktop ‘test’, then made 3 directories under it:

    mkdir -p test/one;test/two;test/three

    Then added 3 files to each:
    cd ~/Desktop
    touch one/1;touch one/2.psd;touch one/.3

    touch two/4.jpg;touch two/5.txt;touch two/6.bak

    touch three/7.old;touch three/8.new;touch three/9.abc

    Using the command listed above worked, even with various extensions (and a ‘dot’ file). Excellent! Thanks eh!

    ~/Desktop user: ls -a1 test
    .
    ..
    .3
    1
    2.psd
    4.jpg
    5.txt
    6.bak
    7.old
    8.new
    9.abc
    one
    three
    two

  4. RT says:

    This may be a noob question, but how does this command behave in the case where duplicate file names exist in different subdirectories? For example:
    ~/TestDirectory/rooty.jpg
    ~/TestDirectory/SampleDirectory1/test-tools.jpg
    ~/TestDirectory/SampleDirectory1/alphabeta-tool.jpg
    ~/TestDirectory/SampleDirectory2/test-tools.jpg
    ~/TestDirectory/SampleDirectory3/test-tools.jpg

    In the above, the same filename exists in 3 different subdirectories and the contents may be different in each file.
    Are the files skipped? or overwritten?

  5. Mark says:

    Any idea how to get a working function with this?

    function flatten() { find $1 -mindepth 2 -type f -exec mv -i {} $1 }

    i.e. flatten blah

  6. ! says:

    Is it possible to move a type of file (jpeg) to the top?

  7. Nord-Jan Vermeer says:

    Nice post, just what I was looking for.
    But, when I do this from terminal all the .DS_Store files require a confirmation. Is there an easy way to exclude those?

  8. brad says:

    Thanks! Great post, anyway to make this action not ask for confirmation to overwrite duplicate files?

  9. whitesiroi says:

    Great, thank you very much for this post.

  10. Sekuri says:

    Thanks, this is the best I’ve found in my search as well. I would suggest some options for improvement:

    1. Instead of `mv -i`, use `mv –backup=numbered`. This will prevent over-writing of same name files and instead append numbers to same names as suffixes.

    2. Instead of `mv -i`, use `cp –backup=numbered –link –no-dereference –preserve=all`. This merely creates a hard link to the existing files in the new directory (it doesn’t copy, even though we’re using cp). The backup option has the same non-over-writing effect as above.

    3. Instead of `find [DIRECTORY] -not -path ‘*/\.DS_Store*’ -mindepth 2 -type f`, use `find [DIRECTORY] -mindepth 2 -type f`. This ignores .DS_Store files. You can also replace ‘*/\.DS_Store*’ with ‘*/\.*’ if you want to ignore all hidden files.

    Once we have the flattened directory, we can delete the hierarchical tree with all subdirectories within safely. If the cp command is used, this deleting is still safe as the files themselves are not removed, only the hard links.

    Here’s as example of the full command:

    find dir/ -not -path ‘*/\.*’ -mindepth 2 -type f -exec cp –backup=numbered –link –no-dereference –preserve=all ‘{}’ dir/ ‘;’

    • Sekuri says:

      In proper code formatting…:

      find dir/ -not -path '*/\.DS_Store*' -mindepth 1 -type f -exec cp --backup=numbered --link --no-dereference --preserve=all '{}' dir/ ';'

  11. Michael says:

    Is it possible to save the files with the names for directories included?
    So in the example above the result would be:

    ~/TestDirectory/rooty.jpg
    ~/TestDirectory/SampleDirectory1-beta-tool-preview.jpg
    ~/TestDirectory/SampleDirectory1-alphabeta-tool.jpg
    ~/TestDirectory/SampleDirectory2-test-tools.jpg
    ~/TestDirectory/SampleDirectory3-test-png.jpg
    ~/TestDirectory/SampleDirectory3-test1.jpg
    ~/TestDirectory/SampleDirectory3-test2.jpg

  12. Alisa says:

    Is there a way to do this but instead of moving… to copy?

    I have it all in folders on my HD but want to move them all to an SD but without the folder structure.

    Thanks.

  13. Jay Baker says:

    We had a similar requirement, and found escaping spaces would work this way, by putting quotes around the whole file name:

    find ./tmp/ -type f | awk ‘{ str=$0; sub(/\.\/tmp\//, “”, str); gsub(/\//, “-“, str); print “cp \”” $0 “\” \”./to/” str “\”” }’ | bash

Leave a Reply

 

Shop for Apple & Mac Deals on Amazon.com

Subscribe to OSXDaily

Subscribe to RSS Subscribe to Twitter Feed Follow on Facebook Subscribe to eMail Updates

Recent Posts