I have several directories with files out of sequence because the titles are displayed before the file number. I want to match and replace the end of all the files, minus the extension, so the number is at the beginning like so.

From this;

A guide to perfect eggs-456704.mp4
Boil an egg-456702.mp4
How to ruin scrambled eggs-456703.mp4
Make green eggs and ham-456701.mp4
Poached eggs-456705.mp4

To this:

456701-Make green eggs and ham.mp4
456702-Boil an egg.mp4
456703-How to ruin scrambled eggs.mp4
456704-A guide to perfect eggs.mp4
456705-Poached eggs.mp4

The titles are random in length for each file with spaces, the numbers are 6 digits before the ".mp4" extension but preceded by a dash after the title.


Here you have a way of doing this. It is untested



for f in "${files[@]}" 
     n="${f##*-}" nn="${n%.*}"
     mv "$f" "$nn-$no_ext.$get_ext"
  • OP mentioned in multiple directories, so for each directories this script should be executed separately ? – αғsнιη Oct 16 '17 at 17:30
  • @αғsнιη you are correct. I kinda wrote it in a hurry and didn't read the question properly. For multiple directories, your code is much better. Your find might need a -name '*.mp4' otherwise files with mp3 extension might also be moved. – Valentin Bajrami Oct 16 '17 at 18:09

Using find and Shell (Bash, ksh, ksh93, mksh, zsh) Pattern substitution expansion.

find * -type f -name "*.mp4" -exec bash -c 'echo mv -v "$1" "${1//[^0-9]}-${1//[\-0-9]}"' _ '{}' \;
  • With ${1//[^0-9]} we are removing everything except a number; The // is global replacement syntax while / is only for first occurrence of number.

  • Then a dash/hyphe -, and with ${1//[\-0-9]} we are removing everything which is a - or number (again assuming only one - and numerical part is there in files name).

We used -exec in above command which is not secure for some reasons1, 2 and here because with Shell Substitution Expansion it will take file's path and cause unexpected rename + it will drop all dashes - in files name!

At the end don't forget to remove the echo above in commands to have actual rename where it used to dry run.

find . -type f -name "*.mp4" -execdir sh -c '
    echo mv -v "$1" "${n%.*}-${fn##*/}.mp4" ' _ '{}' \;

Same as previous command but here we used shell (POSIX sh/bash/Korn/zsh) parameter substitution expansion and this will grantee to keep all dashes - untouched within filesname if any like a file-with -- here-006.file except the last one which should be drop.

We are looking for the files only -type f ends with .mp4 and with -execdir here, find is changing the current directory to the directory where a file found then execute the sh -c ' ... ' within that directory itself.

  • With n="${1##*-}" "cut-up-to-last-prefix": we are removing everything from the begging of filename until last dash - seen and - itself and assign to variable n; The $1 or ${1} is the relative path to ./fileName to the current directory by -execdir sh -c '...' _ '{}' \; returns.

  • With fn="${1%-*}" "cut-up-to-first-suffix": We are removing everything from end of filename until first dash - seen and - itself and assign to variable fn.

    Now n="456704.mp4" and nf="./A guide to perfect eggs" (assuming for first file). Then;

  • With ${n%.*}- we are removing everything from end of fileName until first dot . seen and . itself and then added a dash -. So this will result only digits part of fileName 456704.

  • With ${fn##*/}.mp4 we are removing everything from begging of fileName until last slash / seen and / itself and then added .mp4 at the end. So this will result A guide to perfect eggs.mp4.

1 What are the security issues and race conditions in using find -exec?
2 Why using the '-execdir' action is insecure for directory which is in the PATH?

set "a file blah blah-004.file" \
"b file blah blah blah-002.file" \
"c file blahh blah-003.file" \
"d file blah blah blah blabby blah-001.file" \
"e file blah-005.file"

for name; do
  old=$name                 # save old name
  ext=${name##*.}           # take extension
  name=${name%.*}           # remove extension
  number=${name##*-}        # take number
  name=${name%-*}           # remove number
  new="$number-$name.$ext"  # set new name
  echo mv "$old" "$new"     # test rename

Remove the last echo, if it works.

  • What if there were hundreds of files? – αғsнιη Oct 16 '17 at 17:36
  • @αғsнιη What are you asking? How to remove the set in order to pass the names as arguments? – ceving Oct 16 '17 at 20:11
  • You are using set but this you will do for hundreds of files? this is not a way to add all files into the set. – αғsнιη Oct 17 '17 at 9:10
  • Once again: remove the set and pass the list of names as arguments to the script. Either in the command line or by reading stdin using xargs. The use of set is just a mock-up for testing purposes. – ceving Oct 17 '17 at 9:16

Alternative find + prename (Perl rename) solution:

find -type f -exec prename 's/^(.+?)([^\/]+)-([0-9]+)(\.[^.]+)$/$1$3-$2$4/' '{}' \;
  • 1
    @αғsнιη, if it's just for dashes - see my simple update – RomanPerekhrest Oct 16 '17 at 18:36
  • Another problem is that, this will fail if there is subdirectory including mp4 files together with .mp4 files. I mean a directory with multiple mp4 files and a sub-directory also that it contains mp4 files as well – αғsнιη Oct 16 '17 at 18:41
  • @αғsнιη, paraphrase your sentence, cause "subdirectory including files together with files" - sounds strange – RomanPerekhrest Oct 16 '17 at 18:44
  • a directory containing mp4 files and a sub-directory which that sub-directory also contains mp4 files (nested directories with mp4 files). – αғsнιη Oct 17 '17 at 9:11

This is a portable sh implementation.

find . -name "*[0-9].mp4" |
    sed "s/\(.*\/\)\(.*\)-\([0-9]*\)\.mp4/'\1\2-\3.mp4' '\1\3-\2.mp4'/" |
    xargs -p -L 1 mv -v

The find command is pretty straightforward: it will return only .mp4 files that contain at least one digit before the extension.

The sed command duplicates the filename, sorrounding each filename with single quotes, and then the second filename gets the digit part moved between the relative file path and the filename accordingly (e.g., './a/Boil an egg-456707' './a/456707-Boil an egg.mp4').

The resulting list of current and new filenames are then passed to the xargs command, which will execute mv -v. The -L 1 option is needed so that xarg processes one line at a time. You can remove the -p option from this command and change the options that mv should use.

I tested this implementation with your file list on Ubuntu bash, sh, and Mac OS High Sierra bash and sh, placing files in different subdirectories and adding a few files that didn't qualify.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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