I have a bunch of files in a unix directory that look like the following:


I need to rename them by copying the last three characters of each filename to the front of the filename like this:


NOTE: The extension as well as the filename are variable.

I'm running this on a Solaris box.


5 Answers 5


With zsh:

autoload zmv
zmv -n '*(???).*' '$1_$f'

Remove -n when happy.

  • This was flagged as low quality. Go figure. Nov 12, 2015 at 22:45
  • 1
    @don_crissti zsh is way too terse for its own good
    – roaima
    Nov 12, 2015 at 23:37
  • It would be a better answer if it explained how it works. I can infer that the pattern matched by (???) is referenced by $1, and $f is the original filename, and that -n is a dry-run switch, but others mightn't be able to.
    – DopeGhoti
    Nov 13, 2015 at 18:15
  • @DopeGhoti, in my experience those who want to learn would jump on the opportunity of reading the manual to find out what that does and what more can be one. Those who don't would not even read the few words that would be needed to paraphrase that code. Yes, a few words of explanation would be nice, but we get quite a few of those questions per week and one gets bored after a while. Anybody's welcome to edit the answer to add those words. Aug 12, 2016 at 15:02

So you wish to prepend the last three characters of the file's basename, followed by an underscore, to the front of the filename?

for file in *${EXT}; do # presuming all the filenames have no spaces 
   mv ${file} ${newname}

This will not work in the Korn shell, as the tricks it uses are bash-specific:

ksh[3]: prefix=${base:(-3)}: bad substitution

  • This solution relies on bash string substitution, so the script can't use e. g. the Korn shell.
    – DopeGhoti
    Nov 12, 2015 at 20:29
  • 1
    two errors in your script: 1) base=${file%${EXT}/} should be base=${file%${EXT}} (no trailing /). 2) prefix= ${base:(-3)} should be prefix=${base:(-3)} (no space after =).
    – Lambert
    Nov 12, 2015 at 20:33
  • Ways on ksh: stackoverflow.com/q/24553476/2072269
    – muru
    Nov 12, 2015 at 20:40
  • 3
    It will work on ksh93, not ksh88. bash copied that syntax from ksh. With ksh88 or any POSIX shell prefix=${base#"${base%???}"} Nov 12, 2015 at 22:39

I like to do something like this:

for i in filename_*.txt; do echo "$i"; done |
sed "s/filename_\([0-9]*\)\([0-9]\{3\}\)\.txt/mv -i 'filename_\1\2.txt' '\2_filename_\1\2.txt'/g"

This prints the list of mv commands to accomplish the task. If it prints the correct commands, then just append | sh at the end of the sed command to cause the shell to execute them.

This script uses the sed subsitute command s and the back reference feature to use parts of the matched regular expression in the replacement string.


The parts of the regular expression pattern between \( and \) can be referenced in the replacement string by \N where in is a number.

  • You don't need to do a for i in file*; do echo "$i"; you can just for i in file*.
    – DopeGhoti
    Nov 12, 2015 at 21:35
  • @DopeGhoti I don't think so. Please explain.
    – RobertL
    Nov 12, 2015 at 21:36
  • Oh. OH, I misread your piping. I thought you were taking each file's name, echoing it, and iterating over that, not piping the entire list to sed. I'd still be inclined to just use ls -1 filename_*.txt | sed [...] rather than use a for loop though.
    – DopeGhoti
    Nov 12, 2015 at 21:42
  • @DopeGhoti Yes, either way should work, but the for loop version is safer. The -1 option is redundant, ls prints one filename to a line when output to a pipe. The ls method is much more clear, which also counts. If I was doing this in my own system where I know what's going on, I would use ls.
    – RobertL
    Nov 12, 2015 at 21:51

You're on Solaris so you'll have perl. You might even have rename (or possibly prename):

rename -n 's/^(.*)(.{3})(\.[^.]*)$/$2_$1$2$3/' *.*

Remove -n or change it to -v when you're ready to roll.

  • Rename not available on box.
    – Blair C
    Nov 14, 2015 at 18:50

Pure awk solution: basic idea is to extract last 3 chars of the basename, that means a substring form length - 6 (6 because need to account for the .txt extension) to length - 3. Here I merely extract that substring into VAR first, then create the command complete with mv and two filenames to be ran; finally I ask system function to run the command.


awk 'BEGIN{
           CMD=("mv "ARGV[1]" "VAR"_"ARGV[1]); 
           print CMD; 
          }' filename_1234567.txt 


$ awk 'BEGIN{VAR=sprintf("%s",substr(ARGV[1],length(ARGV[1])-6,3)); CMD=("mv "ARGV[1]" "VAR"_"ARGV[1]); print CMD; system(CMD); close(CMD)}' filename_1234567.txt 
mv filename_1234567.txt 567_filename_1234567.txt
$ ls filename_1234567.txt
ls: cannot access filename_1234567.txt: No such file or directory
$ ls 567_filename_1234567.txt

You must log in to answer this question.

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