Trim file names to leave only last 5 characters

Viewed 76

Wondering if you can help. I have thousands of filenames like this:

2016-IMG123
IMG124
2018-13727-IMG123

I’m trying to create a one liner in terminal to rename all these files in a folder to leave just the last 6 chars.

 for f in *; do mv "$f" "${f%?????}"; done

This was my starting point but I’ve find using the for loops a little confusing. Most example code talks about removing x number of chars from the front, rather than only leaving 6 chars by removing any additional from the start.

5 Answers

So, the idea is the remove a string from the start, which you can do using the variable substitution ${var#string}. But you want to define what string is, and string is everything which remains from var after removing the last 5 characters. This is given by ${var%?????}. So you can use ${var#"${var%?????}"}. Your code would now read:

$ for f in *; do mv -- "$f" "${f#"${f%?????}"}"; done

Remark that this method and many others might fail if your initial filename is shorter than 5 characters. You can protect yourself against that with:

for f in *; do 
  g="${f#"${f%?????}"}"
  [ "$g" ] &&  mv -- "$f" "$g"
done

Note: be advised to clearly quote the string ${f%?????}. This is to avoid unwanted glob-expansions. See the comment of @LeaGris below this question for some details.

Using standard shell variable expansion to extract basename and pattern:

# Iterates globing pattern
# ** Always prepend directory name when doing so **
for source in ./*IMG*; do

  # Removes the directory to get only file name
  base_name=${source##*/}

  # Removes everything before the last dash
  destination="${base_name##*-}"

  # Renames without overwriting existing destinations
  mv --no-clobber -- "${source}" "./${destination}"
done

If you prefer a one-liner of same:

for p in ./*IMG*;do b=${p##*/};d="${b##*-}";mv -n -- "$p" "./$d";done

Assuming no special characters are in the last 6 digits for f in *; do mv "$f" "${f:(-6)}"; done

Because you also need to handle the file extensions, using a bash regex is probably the easiest way:

#!/bin/bash

n=6
regex='^(.*/)?[^/]+([^/]{'$n'}\.[^/.]*|[^/.]{'$n'})$'

for f in ./*.*
do
    [[ $f =~ $regex ]] || continue
    printf '%q %q %q\n' mv "$f" "${BASH_REMATCH[1]}${BASH_REMATCH[2]}"
done

The code will rename the files that have at least 7 characters (excluding the extension). It also handles any path length and files without extension.


Example

For the following file paths:

2016-IMG123.JPEG
./2016-IMG124.MOV
/dir/2018-13727-IMG125.jpg
13727-IMG126
12345.mov

You'll get

mv 2016-IMG123.JPEG IMG123.JPEG
mv ./2016-IMG124.MOV ./IMG124.MOV
mv /dir/2018-13727-IMG125.jpg /dir/IMG125.jpg
mv 13727-IMG126 IMG126

With your shown samples and attempts, please try following find command. I have used . for running and picking files from current path here. Simply going through all the files in path and using bash -c in it to run mv command for renaming files.

find . -type f -exec bash -c 'mv -n -- "$1" "${1:(-6)}"' bash {} \;
Related