Rename files using sed and mv [duplicate]
Asked Answered
P

8

13

I want to rename files in the format:

img_MM-DD-YY_XX.jpg

img_MM-DD-YY_XXX.jpg

to:

newyears_YYYY-MM-DD_XXX.jpg

Where:

  • YYYY = year
  • MM = month
  • DD = day
  • XXX or XX = photo number

I came up with this script but it isn't working:

for filename in ?*.jpg; do
        newFilename=$(echo $filename | \
        sed 's/img_\(.*\)-\(.*\)-\(.*\)_\([0-9][0-9]\)\./newyears_20\3-\1-\2_0\4./;
                s/img_\(.*\)-\(.*\)-\(.*\)_\([0-9][0-9][0-9]\)/newyears_20\3-\1-\2_\4/' -)
        mv $filename $newFilename
done

Any help would be greatly appreciated.

Passade answered 15/4, 2011 at 2:27 Comment(3)
What happens when you run your script? What does for filename in ?*.jpg; do echo $filename; done return?Verbose
I found the problem. Removing the backslash, "\", after the pipe, "|", makes it work. I guess newlines should not be escaped in bash scripts. Is this correct?Passade
the backslash isn't necessary, but shouldn't cause any trouble. Might there have been a space after it, or something like that?Tuchman
A
17

You can try this script in bash:

for filename in *.jpg; do
  newFilename=$(sed -E 's#img_([0-9]{1,2})-([0-9]{1,2})-([0-9]{1,2})_(.*)$#newyears_20\3-\2-\1_\4#' <<< "$filename")
  mv "$filename" "$newFilename"
done

sed -E is supported by gnu sed also.

Arak answered 15/4, 2011 at 2:48 Comment(1)
You also want to use -E on FreeBSD.Whisker
C
2

Without a for loop:

ls | grep 'jpg$' | sed '
#Save the original filename
h
#Do the replacement
s/img_\(.*\)-\(.*\)-\(.*\)_\([0-9][0-9]\)\./newyears_20\3-\1-\2_0\4.//
s/img_\(.*\)-\(.*\)-\(.*\)_\([0-9][0-9][0-9]\)/newyears_20\3-\1-\2_\4//
#Bring the original filename back
x
G
s/^\(.*\)\n\(.*\)$/mv "\1" "\2"' | bash

Omit piping to bash to see the results before mv

Thanks to http://www.gnu.org/software/sed/manual/sed.html#Rename-files-to-lower-case

Cannabin answered 28/2, 2014 at 18:48 Comment(3)
Don't parse ls outputSubdebutante
@GillesQuenot You might give a reason, it would be helpful.Cannabin
@Cannabin There are many; this is a very frequently linked FAQ: mywiki.wooledge.org/ParsingLsIsm
P
1

This trivial variant works for me:

$ cat mapper
for filename in ?*.jpg
do
    newFilename=$(echo $filename | \
    sed -e 's/img_\(.*\)-\(.*\)-\(.*\)_\([0-9][0-9]\)\./newyears_20\3-\1-\2_0\4./' \
        -e 's/img_\(.*\)-\(.*\)-\(.*\)_\([0-9][0-9][0-9]\)/newyears_20\3-\1-\2_\4/')
    echo mv $filename $newFilename
done
$ echo > img_04-23-09_123.jpg
$ echo > img_08-13-08_33.jpg
$ sh mapper
mv img_04-23-09_123.jpg newyears_2009-04-23_123.jpg
mv img_08-13-08_33.jpg newyears_2008-08-13_033.jpg
$

The only difference is the use of the explicit -e options in place of a semi-colon.

Tested on MacOS X 10.6.7.

Proclamation answered 15/4, 2011 at 2:46 Comment(1)
Seems too fragile. Need quotingSubdebutante
A
0

I remember messing around with this sort of thing. I found it often useful to create a script that you execute to do what you want:

ie. output of your script would be a file like:

mv   file1 file2
mv   file3 file4
.....
mv   fileN fileM

To create this, just do a ls | grep date pattern | sed script to stick mv file1 to file2 > myscript

then just execute ./myscript

This way you have a better look at the intermediate output to figure out what's going wrong.

Aerobiology answered 15/4, 2011 at 2:31 Comment(0)
C
0

It could be handled by a purely bash script.

for j in ?*.jpg
do
  n="newyears_20${j:10:2}-${j:4:2}-${j:7:2}";
  if [ ${j:15:1} = "." ];then
    n="${n}_0${j:13}"
  else
    n="${n}${j:12}"
  fi
  mv $j $n
done
Closefitting answered 15/4, 2011 at 3:7 Comment(1)
Seems too fragile. Need quotingSubdebutante
E
0
for f in *.jpg; do
  n=$(sed -r 's/^(.+)-([^_]+)_(.+)\.jpg/\2-\1_\3.jpg/' <<< "${f#img_}")
  mv "$f" "newyears_20$n"
done

The <<< is a bash feature called a here string and ${f#img_} removes the img_ prefix from $f. The sed expression turns img_MM-DD-YY_XX.jpg into YY-MM-DD_XX.jpg by isolating MM-DD into \1, YY into \2 and XX into \3 then forming \2-\1_\3.jpg. Note that no quoting is required in the n assignment.

For safety, for things like this I actually prefer to use echo mv instead of mv, so I can see the command this would generate. Then you can remove echo or pipe the output to sh.

Ethban answered 15/4, 2011 at 18:30 Comment(3)
Seems too fragile. Need quotingSubdebutante
Please add explanations to your answer for long term value, and so future visitors can learn from your solution, and apply th this knowledge to their own coding issues. Code only answers are frowned upon, and less likely to receive upvotes.Shizukoshizuoka
Fixed the quoting, added explanation. Actually, rewrote the whole thing, since the output was all wrong. I guess I didn't test it at the time.Ethban
C
0

You don't need regex for that. Just use rename:

rename img_ newyears_ img_*.jpg

If you want to use mv, try this one-liner:

for filename in img*jpg; do mv -v "$filename" "$(echo "$filename" | sed 's/img_/newyears_/'"); done

You can also do it without sed:

for filename in img*jpg; do mv -v "$filename" "newyears_${filename#*_}"; done

Also since bash for loops break on whitespaces I would use find instead:

find . -name "img*.jpg" -maxdepth 1 -exec rename -v img newyears {} \;

Tested in CentOS 7

Cantoris answered 8/12, 2023 at 9:18 Comment(6)
What works depends on which rename utility you are talking about. There are several, but none of them are standard.Ism
Artificially forcing your three-liner to a one-liner is just making it harder to read. You can then easily replace the newlines with semicolons if you really want to.Ism
Thanks for the edit. I think this particular rename command is so simple that it will work in all of them.Cantoris
If you can point me towards a definition of one-liner I can be more precise in the future. I have considered everything that doesn't need scrolling in my terminal window a one-liner.Cantoris
The conventional formatting is to put the body of the loop in its own block with the for line above and the done line below it.Ism
It's not about simplicity; different rename utilities have entirely different syntax for what to replace and how to specify the replacement. For example, the also popular Perl-based rename would require rename 's/^img_/newyears_/' img_*.jpg. For the record, yours seems to be the util-linux version.Ism
C
-1

Ruby(1.9+)

$ ruby -e 'Dir["img*jpg"].each{|x|File.rename(x,x.gsub(/img_(.*?)-(.*?)-(.*?)\.jpg/,"newyears_20\\2-\\1-\\3.jpg") )}'
Cofer answered 15/4, 2011 at 3:22 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.