-1

How to recursively rename all the files in several layers of subdirectories without changing their extensions?

Below's a toned down version (to save room) of what I've got. For argument's sake, I want all the files to have the same title, yet retain their original extension. There's never more than a single file per directory, so there's no chance of doubling up.

For simplicity, we'll just call them all foo, followed by their current extension.

So just to clarify:
Asset\ 1.pdf, Asset\ 1.png, Asset\ 1@4x.png, Asset\ 1.svg
Will become:
foo.pdf, foo.png, foo.png, foo.svg
And so on in that fashion.


I would typically use parameter expansion and a for loop, like:

for f in */*; do mv "$f" "${f%/*}/foo.${f##*.}"; done  

But it's not recursive. So I would prefer to use something with find..-exec or similar.


~/Desktop/Project/Graphics/
├── Huge
│   ├── PDF
│   │   └── Asset\ 1.pdf
│   ├── PNG
│   │   ├── 1x
│   │   │   └── Asset\ 1.png
│   │   └── 4x
│   │       └── Asset\ 1@4x.png
│   └── SVG
│       └── Asset\ 1.svg
├── Large
│   ├── PDF
│   │   └── ProjectAsset\ 2.pdf
│   ├── PNG
│   │   ├── 1x
│   │   │   └── ProjectAsset\ 2.png
│   │   └── 4x
│   │       └── ProjectAsset\ 2@4x.png
│   └── SVG
│       └── ProjectAsset\ 2.svg
├── Medium
│   ├── PDF
│   │   └── ProjectAsset\ 3.pdf
│   ├── PNG
│   │   ├── 1x
│   │   │   └── ProjectAsset\ 3.png
│   │   └── 4x
│   │       └── ProjectAsset\ 3@4x.png
│   └── SVG
│       └── ProjectAsset\ 3.svg
├── Small
│   ├── PDF
│   │   └── ProjectAsset\ 4.pdf
│   ├── PNG
│   │   ├── 1x
│   │   │   └── ProjectAsset\ 4.png
│   │   └── 4x
│   │       └── ProjectAsset\ 4@4x.png
│   └── SVG
│       └── ProjectAsset\ 4.svg
└── Tiny
    ├── PDF
    │   └── Asset\ 5.pdf
    ├── PNG
    │   ├── 1x
    │   │   └── Asset\ 5.png
    │   └── 4x
    │       └── Asset\ 5@4x.png
    └── SVG
        └── Asset\ 5.svg

30 directories, 20 files
don_crissti
  • 82,805
voices
  • 1,272

5 Answers5

4

It's very similar with find...-exec: invoke a shell so that you can use parameter expansion, extract the PARENT directory and the EXTENSION so that you can construct the new filename as PARENT/NAME.EXTENSION and then move/rename:

find target_dir -name '?*.*' -type f -exec sh -c '
  ret=0
  for file do
    head=${file%/*}
    mv -- "$file" "$head/NAME.${file##*.}" || ret=$?
  done
  exit "$ret"' sh {} +

If you want to dry-run the above, insert an echo before the mv...

Beware it also processes hidden files.


If you have access to zsh you could run:

autoload zmv
zmv -n '(**/)(*.*)' '${1}NAME.${2:e}'

remove the -n if you're happy with the result.

Hidden files are skipped by default, but you can use the (#qD) glob qualifier if you want them processed:

autoload zmv
zmv -n '(**/)(?*.*)(#qD)' '${1}NAME.${2:e}'
don_crissti
  • 82,805
1

If using bash, your idea could walk all directories:

$ shopt -s globstar
$ for f in ./Desktop/**/*; do [[ -f $f ]] && 
           mv -n "$f" "${f%/*}/foo.${f##*.}"; done

Added -n to mv to avoid overwriting existing files (if any).

That could also be done with find (in one call to the shell (faster than one shell per file)):

$ find ./Desktop -type f -exec sh -c '
       for f; do echo mv -n "$f" "${f%/*}/foo.${f##*.}"; done' findsh {} \+

Remove the echo if the command does what you need.

0

Maybe I'm oversimplifying, but, if you know the max depth of the directory hierarchy (here: 3), why not

for f in */* */*/* */*/*/*; do ... ; done 

adding some error checking?

RudiC
  • 8,969
0

Finding all regular files in or below the directory ~/Desktop/Project/Graphics whose names start with Asset 1 and replacing that part of the name with the string foo:

find ~/Desktop/Project/Graphics -type f -name 'Asset 1*' -exec sh -c '
    for pathname do
        dir=${pathname%/*}
        name=${pathname##*/Asset 1}
        mv -i "$pathname" "$dir/foo$name"
    done' sh {} +

In the above, $name will be the part of the original filename with Asset 1 removed from the start.

To replace the whole filename but keep the filename suffix (and also matching any file starting with Asset (with space after the word)):

find ~/Desktop/Project/Graphics -type f -name 'Asset *' -exec sh -c '
    for pathname do
        dir=${pathname%/*}
        name=${pathname##*.}
        mv -i "$pathname" "$dir/foo$name"
    done' sh {} +

Related:

Kusalananda
  • 333,661
0
find ~/Desktop/Project/Graphics/ -type f -exec sh -c 'f={}; mv $f ${f%/*}/foo.${f##*.}' \;

sh means invoking default shell. you can change it to bash, zsh and so on.