Rename files using regular expression in linux
RegexTerminalRenameRegex 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?
find . -type f
outputs file paths (or file names...you control what gets processed by regex here!)-p
prints file paths that were processed by regex,-e
executes inline scriptprint $_
prints the original file name first (independent of-p
)-d "\n"
cuts the input by newline, instead of default space character-n2
prints two elements per linemv
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)
- 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. - In
perl
andxargs
:-0
stdin delimiter is the null byte (rather than space) - 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
mmv (mass-move?)
UseIt'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_?.ext
→ file_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