Rename files using regular expression in linux

RegexTerminalRename

Regex Problem Overview


I have a set of files named like:

Friends - 6x03 - Tow Ross' Denial.srt
Friends - 6x20 - Tow Mac and C.H.E.E.S.E..srt
Friends - 6x05 - Tow Joey's Porshe.srt

and I want to rename them like the following

S06E03.srt
S06E20.srt
S06E05.srt

what should I do to make the job done in linux terminal? I have installed rename but U get errors using the following:

rename -n 's/(\w+) - (\d{1})x(\d{2})*$/S0$2E$3\.srt/' *.srt

Regex Solutions


Solution 1 - Regex

You forgot a dot in front of the asterisk:

rename -n 's/(\w+) - (\d{1})x(\d{2}).*$/S0$2E$3\.srt/' *.srt

On OpenSUSE, RedHat, Gentoo you have to use Perl version of rename. This answer shows how to obtain it. On Arch, the package is called perl-rename.

Solution 2 - Regex

find + perl + xargs + mv

xargs -n2 makes it possible to print two arguments per line. When combined with Perl's print $_ (to print the $STDIN first), it makes for a powerful renaming tool.

find . -type f | perl -pe 'print $_; s/input/output/' | xargs -d "\n" -n2 mv

Results of perl -pe 'print $_; s/OldName/NewName/' | xargs -n2 end up being:

OldName1.ext    NewName1.ext
OldName2.ext    NewName2.ext
OldName3.ext    NewName3.ext
OldName4.ext    NewName4.ext

I did not have Perl's rename readily available on my system.


How does it work?

  1. find . -type f outputs file paths (or file names...you control what gets processed by regex here!)
  2. -p prints file paths that were processed by regex, -e executes inline script
  3. print $_ prints the original file name first (independent of -p)
  4. -d "\n" cuts the input by newline, instead of default space character
  5. -n2 prints two elements per line
  6. mv gets the input of the previous line

My preferred approach, albeit more advanced.

Let's say I want to rename all ".txt" files to be ".md" files:

find . -type f -printf '%P\0' | perl -0 -l0 -pe 'print $_; s/(.*)\.txt/$1\.md/' | xargs -0 -n 2 mv

The magic here is that each process in the pipeline supports the null byte (0x00) that is used as a delimiter as opposed to spaces or newlines. The first aforementioned method uses newlines as separators. Note that I tried to easily support find . without using subprocesses. Be careful here (you might want to check your output of find before you run in through a regular expression match, or worse, a destructive command like mv).

How it works (abridged to include only changes from above)

  1. In find: -printf '%P\0' print only name of files without path followed by null byte. Adjust to your use case-whether matching filenames or entire paths.
  2. In perl and xargs: -0 stdin delimiter is the null byte (rather than space)
  3. In perl: -l0 stdout delimiter is the null byte (in octal 000)

Solution 3 - Regex

Edit: found a better way to list the files without using IFS and ls while still being sh compliant.

I would do a shell script for that:

#!/bin/sh
for file in *.srt; do
  if [ -e "$file" ]; then
    newname=`echo "$file" | sed 's/^.*\([0-9]\+\)x\([0-9]\+\).*$/S0\1E\2.srt/'`
    mv "$file" "$newname"
  fi
done

Previous script:

#!/bin/sh
IFS='
'
for file in `ls -1 *.srt`; do
  newname=`echo "$file" | sed 's/^.*\([0-9]\+\)x\([0-9]\+\).*$/S0\1E\2.srt/'`
  mv "$file" "$newname"
done

Solution 4 - Regex

Use mmv (mass-move?)

It's simple but useful: The * wildcard matches any string (without slashes) and ? matches any character in the string to be matched. Use #X in the replace string to refer to the X-th wildcard match.

In your case:

mmv 'Friends - 6x?? - Tow *.srt' 'S06E#1#2.srt'

Here #1#2 represent the two digits which are captured by ?? (match #1 and #2).
So the following replacement is made:

Friends - 6x?? - Tow *           .srt    matches
Friends - 6x03 - Tow Ross' Denial.srt    which is replaced by
            ↓↓
        S06E03.srt

mmv also offers matching by [ and ] and ;.

You can not only mass rename, but also mass move, copy, append and link files.

See the man page for more!

Personally, I use it to pad numbers such that numbered files appear in the desired order when sorted lexicographically (e.g., 1 appears before 10): file_?.extfile_0#1.ext

Solution 5 - Regex

Not every distro ships a rename utility that supports regexes as used in the examples above - RedHat, Gentoo and their derivatives amongst others.

Alternatives to try to use are perl-rename and mmv.

Solution 6 - Regex

if your linux does not offer rename, you could also use the following:

find . -type f -name "Friends*" -execdir bash -c 'mv "$1" "${1/\w+\s*-\s*(\d)x(\d+).*$/S0\1E\2.srt}"' _ {} \;

i use this snippet quite often to perform substitutions with regex in my console.

i am not very good in shell-stuff, but as far as i understand this code, its explanation would be like: the search results of your find will be passed on to a bash-command (bash -c) where your search result will be inside of $1 as source file. the target that follows is the result of a substitution within a subshell, where the content of $1 (here: just 1 inside your parameter-substituion {1//find/replace}) will also be your search result. the {} passes it on to the content of -execdir

better explanations would be appreciated a lot :)

please note: i only copy-pasted your regex; please test it first with example files. depending on your system you might need to change \d and \w to character classes like [[:digit:]] or [[:alpha:]]. however, \1 should work for the groups.

Solution 7 - Regex

I think the simplest as well as universal way will be using for loop sed and mv. First, you can check your regex substitutions in a pipe:

ls *.srt | sed -E 's/.* ([0-9])x([0-9]{2}) .*(\.srt)/S\1E\2\3/g'

If it prints the correct substitution, just put it in a for loop with mv

for i in $(ls *.srt); do 
    mv $i $(echo $i | sed -E 's/.* ([0-9])x([0-9]{2}) .*(\.srt)/S\1E\2\3/g') 
    done

Solution 8 - Regex

You can use rnm:

rnm -rs '/\w+\s*-\s*(\d)x(\d+).*$/S0\1E\2.srt/' *.srt

Explanation:

  1. -rs : replace string of the form /search_regex/replace_part/modifier
  2. (\d) and (\d+) in (\d)x(\d+) are two captured groupes (\1 and \2 respectively).

More examples here.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionorezvaniView Question on Stackoverflow
Solution 1 - RegexThorView Answer on Stackoverflow
Solution 2 - RegexJonathan KomarView Answer on Stackoverflow
Solution 3 - RegexCreakView Answer on Stackoverflow
Solution 4 - RegexxoxoxView Answer on Stackoverflow
Solution 5 - Regexgerrit_hoekstraView Answer on Stackoverflow
Solution 6 - RegexmeistermuhView Answer on Stackoverflow
Solution 7 - Regexalisson_SoaresView Answer on Stackoverflow
Solution 8 - RegexJahidView Answer on Stackoverflow