2

This question already has an answer here:

this is a unique situation, which can be better explained with an example

Example 1 let's say I have a folder called A, B.mkv and C.srt are two files in the folder, I want to rename these files to A.mkv and A.srt.

practical example would be, a folder called American Sniper (2014) and American.Sniper.2014.1080p.BluRay.x264.YIFY.mkv and American.Sniper.2014.Subtitle.BluRay.ShaAnig.srt are two files in the folder, want to rename these files to American Sniper (2014).mkv and American Sniper (2014).srt

Example 2

This Should be recursive let's say I have a folder called A, B and C are two Sub-folders in the folder A. contents of B and C are like following

Example

Should get converted to

Result

Even when I run the algorithm/script/command on folder A

3rd thing that it should do is, It should Ignore hidden files

example

Example

should result in

Result

lastly it should work on multiple folders at a time

marked as duplicate by mikewhatever, Jacob Vlijm command-line Apr 7 '17 at 14:36

This question has been asked before and already has an answer. If those answers do not fully address your question, please ask a new question.

  • Yes, you absolutely can! – 13dimitar Apr 7 '17 at 10:36
  • @13nilux do you have an answer? do you need more information from my end? – Sumeet Deshmukh Apr 7 '17 at 10:37
  • VOTERS: scripting questions are on-topic on this site. – Sergiy Kolodyazhnyy Apr 7 '17 at 14:35
3

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
  • 1
    last command worked perfectly, thanks alot – Sumeet Deshmukh Apr 7 '17 at 16:25
3

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).

  • I've no idea how the command works can you make it little bit universal (to work on any folder)and explain how to use it in a practical situation. – Sumeet Deshmukh Apr 7 '17 at 12:55

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