1

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

filename_1234567.txt

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

567_filename_1234567.txt

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

I'm running this on a Solaris box.

3

With zsh:

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

Remove -n when happy.

  • This was flagged as low quality. Go figure. – don_crissti Nov 12 '15 at 22:45
  • 1
    @don_crissti zsh is way too terse for its own good – roaima Nov 12 '15 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 '15 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. – Stéphane Chazelas Aug 12 '16 at 15:02
2

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

#!/bin/bash
EXT=.txt
for file in *${EXT}; do # presuming all the filenames have no spaces 
   base=${file%${EXT}}
   prefix=${base:(-3)}
   newname=${prefix}_${base}${EXT}
   mv ${file} ${newname}
done

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 '15 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 '15 at 20:33
  • Ways on ksh: stackoverflow.com/q/24553476/2072269 – muru Nov 12 '15 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%???}"} – Stéphane Chazelas Nov 12 '15 at 22:39
1

I like to do something like this:

#!/bin/sh
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.

s/regex/replacement/

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 '15 at 21:35
  • @DopeGhoti I don't think so. Please explain. – RobertL Nov 12 '15 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 '15 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 '15 at 21:51
1

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 '15 at 18:50
1

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.

Code:

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 

Demo:

$ 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
567_filename_1234567.txt
$ 

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.