5

I have a directory on a Deepin (Debian-based) system which contains video files, something like this:

SL Benfica - Match 1 vs FC Porto.mp4
SL Benfica - Match 2 vs FC Porto.mp4
...
SL Benfica - Match 20 vs FC Porto.mp4
...
SL Benfica - Match 100 vs FC Porto.mp4
SL Benfica - Match 101 vs FC Porto.mp4

I would like to add zeros before the numbers in the middle so that they are sorted as in

SL Benfica - Match 001 vs FC Porto.mp4
SL Benfica - Match 002 vs FC Porto.mp4
...
SL Benfica - Match 020 vs FC Porto.mp4
...
SL Benfica - Match 100 vs FC Porto.mp4
SL Benfica - Match 101 vs FC Porto.mp4

I'm looking for a general command that does not depend on patterns. Just to search numbers like 5 and 20, and change them to 005 and 020, anywhere in the filename, even in the end or beginning of it.

4
  • 3
    that means extension mp004, right?
    – ruud
    Commented Jun 7, 2020 at 12:41
  • 1
    @ruud no, look at the desired output.
    – terdon
    Commented Jun 7, 2020 at 12:46
  • @terdon: The question literally says "even in the end". It's probably one of those "count the 'e's in this sentence" type of questions. It's easy to overlook the 4 in mp4 for humans, but to computers 4 is a number.
    – MSalters
    Commented Jun 8, 2020 at 10:53
  • 1
    @MSalters yes, I know, which is why all answers take that into account. But the output is right there and hasn't changed the extension, neither does the answer that the OP accepted, so I don't see an issue here.
    – terdon
    Commented Jun 8, 2020 at 10:57

4 Answers 4

10

You can use perl-rename (which should be installable with sudo apt install rename on Debian-based systems). Using these files as input:

$ ls -1
'anotherFile.m4a'    
'file 1 with 12 many 100 numbers 3.mp4'
'SL Benfica - Match 101 vs FC Porto.mp4'
'SL Benfica - Match 20 vs FC Porto.mp4'
'SL Benfica - Match 2 vs FC Porto.mp4'

You can run:

rename 's/(\d+)(?=.*\.)/sprintf("%03d",$1)/eg' *

Which will rename them to:

'anotherFile.m4a'
'file 001 with 012 many 100 numbers 003.mp4'
'SL Benfica - Match 002 vs FC Porto.mp4'
'SL Benfica - Match 020 vs FC Porto.mp4'
'SL Benfica - Match 101 vs FC Porto.mp4'

Note how the 100 was left unchanged, and the rest were padded. Also note how the 4 in .m4a and mp4 was not affected.

IMPORTANT: run the command with -n first to see what will happen before actually renaming the files:

rename -n 's/(\d+)(?=.*\.)/sprintf("%03d",$1)/eg' *

The regular expression looks for stretches one or more digits (\d+) that come before at least one . (?=.*\.). This is to avoid changing a number found in the extension. However, that will not work if your file names have no extension. If you have files like that, use this instead to just pad all numbers:

rename 's/(\d+)/sprintf("%03d",$1)/eg' *

The s/old/new/ is the substitution operator which will replace old with new. Here, because the (\d+) is in parentheses, whatever is matched will be captured and will then be available as $1 on the right hand side of the substitution. We therefore replace the digits with themselves 0 padded ( sprintf("%03d", $number) will print $number padded with 0s until its length is 3). Finally, the flag e lets us use expressions (here, sprintf) and g makes the substitution global, for all matches on the input line (file name).

Note that if you have a number that is already 0-padded with more than 3 0s, this will trim it to 3-0 padded. So file 00000001.mp3 will become file 001.mp3.

1
  • The explantion of the regex used can be found in this link Commented Jun 7, 2020 at 14:50
7

With zsh:

autoload -Uz zmv # best in ~/.zshrc
zmv -n '(*).mp4' '${1//(#m)<->/${(l[3][0])MATCH}}.mp4'

Remove the -n (dry-run) if happy.

  • zmv renames files based on zsh glob patterns. Here (*).mp4 matches filenames that end in .mp4, the part before .mp4 is captured so it can be used as $1 in the replacement.
  • ${1//pattern/replacement}: ksh operator to perform replacement on a parameter (here $1) expansion.
  • Here the pattern is (#m)<->, a zsh extendedglob pattern. (#m) causes the matched text to be available as $MATCH in the replacement, <->, simplest form of <x-y> number matching operator, matches any sequence of 1 or more decimal digits. Same as [0-9]##.
  • ${(l[3][0])param} uses the l[length][padstring] parameter expansion flag to left-pad the expansion of $MATCH with 0s to length 3 (note that it would also truncate numbers to length 3).

Avoiding truncation is possible, though it gets a bit more convoluted:

zmv -n '(*).mp4' '${1//(#m)<->/${(l[$#MATCH > 3 ? $#MATCH : 3][0])MATCH}}.mp4'

Instead of padding+truncating to length 3, we pad+truncate to a length which is the maximum of 3 and the length of the string to pad. Alternatively you could use the max() math function from the autoloadable zmathfunc function:

autoload -Uz zmv zmathfunc; zmathfunc
zmv -n '(*).mp4' '${1//(#m)<->/${(l[max(3, $#MATCH)][0])MATCH}}.mp4'
1
  • Thank you but I do not have zsh. I would love to use it with Tilix but I'm unable to install it.
    – mykahveli
    Commented Jun 7, 2020 at 13:27
4

With GNU sed, you can do (even with no extension):

sed -E \
-e 'h;s/(.*)(\.[^.]*$)/\2/;ta;z;:a;x;s//\1/' \
-e 's/([0-9]{1,})/\n000\1\n/g' \
-e 's/\n[0-9]*([0-9]{3})\n/\1/g' \
-e 'G;s/\n//g' file

No loops only regexes.

The first regex will match

  • 'h;s/(.*)(\.[^.]*$)/\2/;ta;z;:a;x;s//\1/' place the extension in the hold space (if there was one). Leave the rest in the pattern space.
  • 's/([0-9]{1,})/\n000\1\n/g' Place each stream of numbers in separate lines prepending them with 3 zeros.

After each number to modify has been extended to have leading zeros and being in their own line, do:

  • 's/\n[0-9]*([0-9]{3})\n/\1/g' Extract the last 3 digits from each line of digits.
  • 'G;s/\n//' re-attach the extension captured at the start.

If the source list is:

➤ cat file
1- 23.m2
ATestFile.mp4
SomeFile.m4a
AFileWithNumbers23inside.mp4
File 1 with 12 some 100 numbers 3.mp4
SL Benfica - Match 1 vs FC Porto.mp4
SL Benfica - Match 2 vs FC Porto.mp4
SL Benfica - Match 20 vs FC Porto.mp4
SL Benfica - Match 101 vs FC Porto.mp4

The result will be:

001- 023.m2
ATestFile.mp4
SomeFile.m4a
AFileWithNumbers023inside.mp4
File 001 with 012 some 100 numbers 003.mp4
SL Benfica - Match 001 vs FC Porto.mp4
SL Benfica - Match 002 vs FC Porto.mp4
SL Benfica - Match 020 vs FC Porto.mp4
SL Benfica - Match 101 vs FC Porto.mp4

So, to rename files (filenames should not contain newlines and the extension should follow a dot):

#!/bin/bash

for old in *; do
    new=$(  printf '%s\n' "$old" | 
                sed -E \
                    -e 'h;s/(.*)(\.[^.]*$)/\2/;ta;z;:a;x;s//\1/' \
                    -e 's/([0-9]{1,})/\n000\1\n/g' \
                    -e 's/\n[0-9]*([0-9]{3})\n/\1/g' \
                    -e 'G;s/\n//'
         );
    if [[ ! -f $new ]]; then
        echo \
            mv "$old" "$new";
    fi
done

Remove the echo after you are satisfied with the script to actually change the files.

1
  • Worked perfectly! Thank you very much!
    – mykahveli
    Commented Jun 7, 2020 at 20:04
-1

Try this

$ ls SL*mp4 | awk -v mvCmd='mv "%s" "%s"\n' '{ old=$0; $5=sprintf("%3.3d",$5); printf mvCmd,old,$0 }'

If the new file name is ok, redo the command pipe'ing the output to a shell like bash

The command stores the original filename (old), replace the fifth column ($5) with a printf'ed one (three numbers, no decimals, starting with zeroes), then prints the mv (rename) command with quotes for old and new names.

You have to pipe the output to bash because awk itself doesn't have a file rename command.

3
  • Did not work. Terminal says: ls: cannot access 'SL*mp4': No such file or directory
    – mykahveli
    Commented Jun 7, 2020 at 13:07
  • 2
    This has the classic problems with parsing ls, (it will fail if a file name contains a newline, for example). It also fails if there are either more than one numbers or no numbers. And it also assumes the number must be the 5th field, but the OP is asking for a general solution.
    – terdon
    Commented Jun 7, 2020 at 13:09
  • You have to run the command inside the directory where the files are, @mykahveli Commented Jun 8, 2020 at 15:57

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .