Bash Shell Script – Flattening Directory Hierarchy Preserving Directory Names

bashdirectoryrenameshell-scriptzsh

I basically want to go from this:

.
├── Alan Walker
│   ├── Different World
│   │   ├── 01 Intro.mp3
│   │   ├── 02 Lost Control.mp3
│   │   └── cover.jpg
│   └── Same World
│       ├── 01 Intro.mp3
│       └── 02 Found Control.mp3
├── Aurora
│   └── Infections Of A Different Kind Step 1
│       ├── 01 Queendom.lrc
│       ├── 02 Forgotten Love.lrc
│       └── 03 Gentle Earthquakes.mp3
└── Guns N' Roses
    └── Use Your Illusion I
        ├── 01 Right Next Door To Hell.lrc
        ├── 01 Right Next Door To Hell.mp3
        ├── 02 Dust N' Bones.lrc
        └── 02 Dust N' Bones.mp3

to this:

.
├── Alan Walker - Different World
│   ├── 01 Intro.mp3
│   ├── 02 Lost Control.mp3
│   └── cover.jpg
├── Alan Walker - Same World
│   ├── 01 Intro.mp3
│   └── 02 Found Control.mp3
├── Aurora - Infections Of A Different Kind Step 1
│   ├── 01 Queendom.lrc
│   ├── 02 Forgotten Love.lrc
│   └── 03 Gentle Earthquakes.mp3
└── Guns N' Roses - Use Your Illusion I
    ├── 01 Right Next Door To Hell.lrc
    ├── 01 Right Next Door To Hell.mp3
    ├── 02 Dust N' Bones.lrc
    └── 02 Dust N' Bones.mp3

None of the existing solutions I could find included renaming the directory itself. It'd be great to be able to do this with zmv, but I can't figure out how to.

Best Answer

Zsh

Untested:

zmv -Q '(*)/(*)(/)' '$1 - $2'
rmdir -- *(/^F)

The second line removes all empty directories, even those that didn't have a file before. It's possible to work around this with a custom mv wrapper that records what directories it moves things from.

Note that this traverses symbolic links to directories in the current directory.

Linux rename utility

Untested.

rename / ' - ' */*/
rmdir -- */ 2>/dev/null

Note that this traverses symbolic links to directories in the current directory and in its subdirectories. The second line removes all empty directories, even those that didn't have a file before.

Perl rename script

Untested.

prename 's~/~ - ~' */*/
rmdir -- */ 2>/dev/null

Note that this traverses symbolic links to directories in the current directory and in its subdirectories. The second line removes all empty directories, even those that didn't have a file before.

Here's a more complex approach that only removes directories that it renamed something from. Again, untested.

prename 's~([^/]+)/~$1 - ~ and ++$d{$1}; END {map {rmdir} keys %d}' */*/
Related Question