How to Flatten Complex Folder Structures with Duplicate File Names

command linedirectoryfileslinux

I have a folder with a complicated folder structure:

├── folder1
│   ├── 0001.jpg
│   └── 0002.jpg
├── folder2
│   ├── 0001.jpg
│   └── 0002.jpg
├── folder3
│    └── folder4
│         ├── 0001.jpg
│         └── 0002.jpg
└── folder5
     └── folder6
           └── folder7
                ├── 0001.jpg
                └── 0002.jpg   

I would like to flatten the the folder structure such that all the files reside in the parent directory with unique names such as folder1_0001.jpg, folder1_0002.jpg… folder5_folder6_folder7_0001.jpg etc.

I have attempted to use the code suggested in "Flattening folder structure"

$ find */ -type f -exec bash -c 'file=${1#./}; echo mv "$file" "${file//\//_}"' _ '{}' \;

The echo demonstrates that it is working:

mv folder3/folder4/000098.jpg folder3_folder4_000098.jpg

But the output files are not placed in the parent directory. I have searched the entire drive and cannot find the output files.

I have also attempted "Flatten a folder structure to a file name in Bash"

$ find . -type f -name "*.jpg" | sed 'h;y/\//_/;H;g;s/\n/ /g;s/^/cp -v /' | sh

-v demonstrates that it is working:

‘./folder3/folder4/000098.jpg’ -> ‘._folder3_folder4_000098.jpg’

However the output creates hidden files in the parent directory, this complicates my workflow. I am able to view these hidden files in the parent directory using ls -a

I have also tried the suggested code below from "Renaming Duplicate Files with Flatten Folders Command"

find . -mindepth 2 -type f | xargs mv --backup=numbered -t . && find . -type d -empty -delete

But the command overwrites files with similar file names.

Any suggestions on how to flatten a complicated folder structure without overwriting files with similar names? The current solutions seem to only work on folder structures one layer deep.

My ultimate goal is to convert the unique names into sequential numbers as described in "Renaming files in a folder to sequential numbers"

 a=1
  for i in *.jpg; do
  new=$(printf "%04d.jpg" "$a") #04 pad to length of 4
  mv -- "$i" "$new"
  let a=a+1
 done

Best Answer

I have no idea why the first solution in your question wouldn't work. I can only assume you forgot to remove the echo. Be that as it may, here's another approach that should also do what you need, assuming you're running bash:

shopt -s globstar
for i in **/*jpg; do mv "$i" "${i//\//_}"; done

Explanation

  • The shopt -s globstar turns on bash's globstar feature which makes ** recursively match any number of directories or files.
  • for i in **/*jpg; will iterate over all files (or directories) whose name ends in .jpg, saving each as $i.
  • "${i//\//_}" is the name of the current file (or directory) with all instances of / replaced with _.

If you can also have directories with names ending in .jpg and want to skip them, do this instead:

shopt -s globstar
for i in **/*jpg; do [ -f "$i" ] && echo mv "$i" "${i//\//_}"; done

And for all files, irrespective of extension:

shopt -s globstar
for i in **/*; do [ -f "$i" ] && echo mv "$i" "${i//\//_}"; done
Related Question