Can I rename files within a folder with the name of the parent folder? [duplicate]

Solution 1:

There are multiple ways to approach this question. Please read instructions carefully for best results.

In this answer:

  1. Python approach
  2. find + bash approach
  3. bash-only approach with globstar

1. Python solution

Python is a quite powerful language for system administration, and traversing directory tree can be done via os.walk() function. In the script presented below we're doing exactly that - we're finding all files and operating on each one of them, by determining full path to each file, file's extension, and renaming it via os.rename() function.

Script contents

#!/usr/bin/env python3
import os,sys

def get_all_files(treeroot):
    for dir,subdirs,files in os.walk(treeroot):
         for f in files: 
             if f in __file__: continue
             fullpath = os.path.realpath( os.path.join(dir,f) )
             extension = fullpath.split('.')[-1]
             newpath = os.path.join(dir, dir + '.' + extension)
             print('Move ' + fullpath + ' to ' + newpath   )
             # os.rename(fullpath,newpath)


def main():
    top_dir="."
    # If directory not given, assume cwd
    if len(sys.argv) == 2: top_dir=sys.argv[1]
    get_all_files(top_dir)

if __name__ == '__main__' : main()

NOTE: very very important is that to actually rename the files you need to remove # before # os.rename(fullpath,newpath).

Setting up the script

All the standard rules for scripting apply: - save it as add_location_name.py in the top-most directory - make executable with chmod +x ./add_location_name.py - run with ./add_location_name.py

Testing the script

Here's an example of how this works in practice. I've created a directory with two others, Movie A (2016) and Movie B (2016). Inside them they both have two files. Our script lives in the same directory:

$ tree                                                        
.
├── add_location_name.py
├── Movie A (2014)
│   ├── filea.mkv
│   └── fileb.srt
└── Movie B (2016)
    ├── filea.mkv
    └── fileb.srt

So when we run the script, we shall see the following output:

$ ./add_location_name.py                                                                                              
Move /home/xieerqi/testdir/Movie A (2014)/fileb.srt to ./Movie A (2014)/./Movie A (2014).srt
Move /home/xieerqi/testdir/Movie A (2014)/filea.mkv to ./Movie A (2014)/./Movie A (2014).mkv
Move /home/xieerqi/testdir/Movie B (2016)/fileb.srt to ./Movie B (2016)/./Movie B (2016).srt
Move /home/xieerqi/testdir/Movie B (2016)/filea.mkv to ./Movie B (2016)/./Movie B (2016).mkv

2. Solution via find command and -exec flag

find command is useful in many ways, particular when performing operations on multiple levels of directory tree. In this particular case, we can use it to filter out all files, and perform a renaming operation on them.

First of all, let me give you the solution:

find -type f -exec bash -c 'fp=$(dirname "$1");fn=$(basename "$fp");px="${1##*.}";mv "$1" "$fp"/"$fn"."$px"' sh "{}" \;

Looks long and scary, right ? But don't worry, lets go over how it works.

Dissecting the command

First of all you need to recognize that there are two things going on: the find command locates all the files and bash part is what actually does the renaming part. On the outside, what we see is the simple command:

find -type f -exec <COMMAND> \;

This only finds all files ( no directories or symlinks ) and calls some other command whenever it finds a file. In this case the specific COMMAND that we have is bash.

So what does bash -c 'fp=$(dirname "$1");fn=$(basename "$fp");px="${1##*.}";mv "$1" "$fp"/"$fn"."$px"' sh "{}" part do ? Well, first of all recognize the structure: bash -c 'command1;command2' arg0 arg1. Whenever -c flag is used the first command line argument arg0 will be set as $0 , shell name, so it is irrelevant, but "{}" is important. That is the quoted placeholder for filename that find will pass as argument to bash.

On the inside of the bash command we extract the file path fp=$(dirname "$1") , the directory name fn=$(basename "$fp"), and file extension or prefix px="${1##*.}". All this works out really nicely since we're running the command from top-most directory (very important !). Finally, the mv "$1" "$fp"/"$fn"."$px"' sh "{}" will rename the original file that find gave us to new filename what we build up with "$fp"/"$fn"."$px" using all those variables.

Example of operation

The test of the command is performed on the same directory as before:

$ tree
.
├── Movie A (2014)
│   ├── filea.mkv
│   └── fileb.srt
└── Movie B (2016)
    ├── filea.mkv
    └── fileb.srt

2 directories, 4 files

And if we run the command, with echo instead of mv we can see that each filename is renamed respectively.

$ find -type f -exec bash -c 'fp=$(dirname "$1");fn=$(basename "$fp");px="${1##*.}";echo "$1" "$fp"/"$fn"."$px"' sh ">
./Movie A (2014)/fileb.srt ./Movie A (2014)/Movie A (2014).srt
./Movie A (2014)/filea.mkv ./Movie A (2014)/Movie A (2014).mkv
./Movie B (2016)/fileb.srt ./Movie B (2016)/Movie B (2016).srt
./Movie B (2016)/filea.mkv ./Movie B (2016)/Movie B (2016).mkv

Remember: the above command uses echo only for testing. When you use mv there's no output, so the command is silent.


3.Simpler approach: Bash and glob star

The above two approaches use recursive tree traversal. Since in your example you only have two levels in directory tree (movie directory and files), we can simplify our previous shell command like so:

for f in */* ;do fp=$(dirname "$f"); ext="${f##*.}" ; echo "$f" "$fp"/"$fp"."$ext" ;done

Again , same idea - replace echo with mv when you are sure that it works properly.

Test results are the same:

$ tree                                                                                                                
.
├── add_location_name.py
├── Movie A (2014)
│   ├── filea.mkv
│   └── fileb.srt
└── Movie B (2016)
    ├── filea.mkv
    └── fileb.srt

2 directories, 5 files
$ for f in */* ;do fp=$(dirname "$f"); ext="${f##*.}" ; echo "$f" "$fp"/"$fp"."$ext" ;done                            
Movie A (2014)/filea.mkv Movie A (2014)/Movie A (2014).mkv
Movie A (2014)/fileb.srt Movie A (2014)/Movie A (2014).srt
Movie B (2016)/filea.mkv Movie B (2016)/Movie B (2016).mkv
Movie B (2016)/fileb.srt Movie B (2016)/Movie B (2016).srt

Solution 2:

Given

$ tree
.
└── A
    ├── B
    │   ├── somefile
    │   ├── T.txt
    │   ├── X.srt
    │   └── Z.mkv
    └── C
        ├── somefile
        ├── T.txt
        ├── W.mkv
        └── Y.srt

3 directories, 8 files

then

$ find A -type f \( -name '*.mkv' -o -name '*.srt' \) -not -name '.*' -exec sh -c '
  for f; do 
    dir="${f%/*}" ; ext="${f##*.}" ; new="${dir##*/}"
    echo mv -- "$f" "${dir}/${new}.${ext}"
  done' sh {} +
mv -- A/C/Y.srt A/C/C.srt
mv -- A/C/W.mkv A/C/C.mkv
mv -- A/B/Z.mkv A/B/B.mkv
mv -- A/B/X.srt A/B/B.srt

(remove the echo once you're sure it is going to do what you want).