6

I am quite new to bash scripting and hoping that someone will help me out.

I have folders that include images named like so:

file_001.jpeg  
file_002.jpeg  
file_003.jpeg  

I'm trying to write a bash function that takes two arguments and renames files like so:

photo-argument1-argument2_001.jpeg  

e.g.

photo-christmas-2014_001.jpeg  

Here's what I have:

function rename () {
  title="photo"
  h="-"
  u="_"
  new=$title$h$1$h$2$u
  for file in *.jpeg; do
    mv -v "$file" "$new"
  done
}

so running rename birthday 2015 produces photo-birthday-2015_,for example.

There's just one problem - how do I append numbers to the new filename? Either pulling out the existing numbers in the filename or generating new ones would be fine as far as results go, but which is the better/easier way, and how would I go about making this happen?

2
  • Edit your question and add some real input filenames and your desired renamed filenames.
    – heemayl
    Sep 19 '16 at 9:37
  • Dear heemayl, I edited to make it clearer, i hope it is more understandable now.
    – arokath
    Sep 21 '16 at 0:39
8

You can get bash to count for you just by telling it where to start, like this:

$ n=0
$ echo $((++n))
1
$ echo $((++n))
2
$ echo $((++n))
3

You can use printf %03d to format the result of $((++n)) as 001 002 003 and 010, 011 etc, for more accurate sorting. Also, there's no point setting - and _ as variables since they are not going to vary. Your function could be like this:

function rename () {
  title="photo"
  n=0
  for file in *.jpeg; do
   printf -v new "$title-$1-$2_%03d.jpeg" "$((++n))"
   mv -v -- "$file" "$new"
  done
}

Which does:

$ rename birthday 2015
'file_001.jpeg' -> 'photo-birthday-2015_001.jpeg'
'file_002.jpeg' -> 'photo-birthday-2015_002.jpeg'
'file_003.jpeg' -> 'photo-birthday-2015_003.jpeg'

But there is already a useful rename command in Ubuntu, so you may want to use a different name :)

3
  • 2
    FYI function names take precedence over binary and aliases names,so you're correct - OP might wanna use different name, but it's not required. Sep 19 '16 at 10:43
  • 1
    @Zanna, Good call on renaming the function to avoid confusion! This worked perfectly, I appreciate your help so much! However I do still have some confusions. I understand using printf to set new, + add numbering. Obviously, "$((++n))" increments n, but I don't really understand how this works - why is it incremented inside printf rather than after mv? Also, in mv -v -- "$file" "$new", what does the double-dash (--) mean? The man page for mv did not enlighten me. Sorry to be so full of questions, I wish to understand the concepts rather than just cut&paste ;)
    – arokath
    Sep 21 '16 at 1:00
  • @arokath no worries, I always want to understand too :) printf formats numbers, so we do the incrementing there so we can print the numbers as 001 and 010 instead of 1 and 10 etc, the incremented number replaces %03d. In that line we are defining the variable to reference in our final mv. The double dash is for safety, to tell bash we have given all the options we want, & if there happens to be a filename that starts with a - and a letter, then it should not be interpreted as an option to mv. This is especially useful when doing rm - you might have a file called -rf...
    – Zanna
    Sep 21 '16 at 4:24
3

Slightly lengthy but usable python alternative. Assuming your naming is consistent with what you have in the example, you could use this function in your ~/.bashrc. ( Be mindful of the "\" characters. There must be no space after then, only newline )

rename_photos()
{
    python -c 'import os,re,sys; \
    [ os.rename(f, "photo_"+sys.argv[1]+\
      "_"+sys.argv[2]+"_"+re.split("[_.]",f)[-2]+\
      "."+re.split("[_.]",f)[-1])  \
      for f in os.listdir(".") \
    ]' "$1" "$2"
}

And call like

rename_photos arg1 arg2

Assuming your naming is consistent, what this does is splits filename into parts at "." and "_" and renames them via os.rename function

For instance:

$ ls
file_001.jpeg  file_002.jpeg  file_003.jpeg
$ rename_photos birthday 2016
$ ls
photo_birthday_2016_001.jpeg  photo_birthday_2016_002.jpeg  photo_birthday_2016_003.jpeg

Script version

Without making it overly complex, here's a small script that does the same function. There's plenty of comment to explain what each part does. The script itself has to be stored in one of the directories that belong in your $PATH variable, for instance in your ~/bin folder. Otherwise, it performs exactly the same function ( in addition to printing new filenames )

#!/usr/bin/env python
import os
import re
import sys

# The operates on all files in the current working
# directory, so you would have to cd to your folder
# and run script there. 
for item in os.listdir("."):

    # We don't wanna rename the script itself ( just in case ) and
    # we don't
    if os.path.join("./" + item) != __file__ and \
       os.path.isfile("./" + item):
           title="photo_"

           # Old name can be split into parts using _ and . as 
           # separators, so we only take items between those as one.
           # The last item (-1) is extension, and the one
           # before extension is number
           old_name = re.split("[_.]",item)
           new_name = sys.argv[1] + sys.argv[2] + "_" \
                      + old_name[-2] + "." + old_name[-1]

           print(new_name)
           os.rename(item,new_name)
4
  • I personally think your answer would benefit from an 'ungolfed' version of your script. Great answer, by the way.
    – grooveplex
    Sep 19 '16 at 19:10
  • Yeah, I considered putting it all as one script but turned into onliner just for OPs convenience. I'll make an edit to it. Thanks Sep 19 '16 at 19:33
  • Thank you so much for your reply. This looks great, however I can't really understand it well (I'm pretty n00b). I am trying to increase my knowledge at the moment, and I will definitely look at it again when I have a bit more scope for understanding! Thanks again.
    – arokath
    Sep 21 '16 at 1:11
  • Hey, arokath and gvooveplex ! I've added a script version, with plenty of comments, so hopefully it's more clear. Sep 21 '16 at 2:10
3

What I understood, you just want to add sequential numbers to the end. I would personally do this

 function rename () {
  count=1           #The first photo will get number 1 to the end
  title="photo"
  h="-"
  u="_"
  new=$title$h$1$h$2$u
  for file in *.jpeg; do
    if [ $count -lt 10 ]; then
        mv -v "$file" "${new}00$count" #numbers 1-9 will be like 001 002..
    elif [ $count -lt 100 ]; then
        mv -v "$file" "${new}0$count" #numbers 10-99 like 022 056 etc..
    else
        mv -v "$file" "$new$count"
    fi
    count=$[ $count + 1 ]      #count variable gets bigger by one 
  done
}

When the files are renamed, they will get sequential numbers with padding to the end. This could be done differently like indicated in other answers, but I find this pretty easy to understand in case a person doesn't have very good knowledge of bash scripting

Hope this helps!

2
  • This worked great, however it didn't add padding numbers, which will cause sorting problems for me. I will see if I can adapt it to include those. Thank you so much for your answer.
    – arokath
    Sep 21 '16 at 1:10
  • @arokath Made some improvements. Now it adds padding too. If I understood how sequential numbers with padding work ;)
    – jiipeezz
    Sep 21 '16 at 8:34
1

You can decide for yourself which is the easier way.

Extracting old numbers

You can get the numbers in your filename with a command like grep:

number=$(egrep -o '[0-9]+' <<< "$file")

The command above saves any sequence of digits (matched by the pattern '[0-9]+') in the variable file (passed in to grep through <<<) to the variable number.

Generating new numbers

You can try something like the below:

number=0 # initialize number to 0
for file in *.jpeg; do
    ... # do some stuff here
    ((number+=1)) # adds 1 to the variable number
done

This generates a number starting from 0 for each of your files.

It may be useful to have numbers formated with a fixed width (i.e. 001 instead of 1) for sorting purposes. In which case you can use printf to get a desired output:

number_formatted="$(printf "%03d" "$number")"

The code above formats "12" as "012" and "321" as "321". The "%03d" specifies the format (0 means prepend 0 to the number, 3 specifies 3 total digits printed, including any 0s).

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.