2

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
  • I don't know what's going on with the duplicate questions, but I just merged the old one into this one and cleared the comments – Michael Mrozek Jan 7 '16 at 20:40
  • 1
    You did try the first suggestion without the echo (find */ -type f -exec bash -c 'file=${1#./}; mv "$file" "${file//\//_}"' _ '{}' \;), right? I just did on the exact folder structure you described and it worked as expected. – terdon Jan 8 '16 at 10:41
  • To avoid the problem with clashing names, first walk through the tree and deal with uses of the intended separator character you wish to use. E.g if you already have a file called folder1/folder4_foo.jpg that's a problem, because it will clash with folder1/folder4/foo.jpg. Maybe that's a problem even if there is no clash; you don't know which underscores came from the directory structure and which were original. – Kaz Jan 8 '16 at 20:58
4

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
  • running $ bash flat_script.sh produces the following error flat_script.sh: line 3: shopt: gobstar: invalid shell option name mv: cannot stat ‘**/*jpg’: No such file or directory – Andrew Brown Jan 8 '16 at 15:09
  • @AndrewBrown sorry, had a typo. It should have been globstar. Try the updated version. And there's no reason to save it in a script, just copy/paste directly into your terminal. – terdon Jan 8 '16 at 15:19
2

If you have the Perl rename (sometimes called prename) you can do this:

find folder* -type f -name '*.jpg' -exec rename 's!/!_!g' {} \;

This takes each filename path and replaces all occurrences of / with _.

If you actually want to rename the files to a series of consecutive four digit filenames, you may be able to use this on your original dataset. It will work only where the directory and file names contain regular alphanumerics (specifically, it will fail if a space, shell punctuation, or other whitespace is present):

rename 's!.*!sprintf "%04d.jpg", ++$a!e' $(find folder* -type f -name '*.jpg' | sort)

Use rename -n ... to see what would happen with it changing anything, or use rename -v ... to watch it happening as it runs.

1
pax -rws'|/|_|g' folder*/ .

...will work so long as there is no way to overlap te filenames. To do this more safely use the -l link option and mirror the tree to another path then inspect the new tree before removing the old.

-2

I was successful with the following script:

#!/bin/sh

for folder in $(ls $1)
do
  for file in $(ls ${folder})
  do
    mv ${folder}/${file} $1/${folder}_${file}
  done
  rmdir ${folder}
done
  • This method deleted all the files and folders. Presumably because of the complexity of the folder structure. I have edit the question above to be more explicit. – Andrew Brown Jan 7 '16 at 15:27

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.