Hey guys!
Happy New Year! I am glad to be back this year with some interesting news. Thank you for blasting my mailbox with your questions and suggestions! I will do my best to answer all of them as soon as I can!
One of the readers asked me a question that I think is very interesting. Moreover, I have faced the same issue recently, and today I want to share it with you.
So topic of today is:
How to ensure filename is always unique ? [Ruby]
Why is this a problem?
Imagine you are copying a file ‘a.jpg’ in the directory where this file already exist. Some of the systems I know just give you a validation error and ask to rename a file. But it is a good user experience ? I don’t think so.
So what are our options to tackle this issue ?
Option 1. Append some random string to the filename e.g ‘a-asdsds.jpg’
But is it a solution? How random are those strings ? Does this look appealing ?
I think it is a solution, but a pretty ugly one.
Option 2. Append a number to the filename that is incremented with every creation
This solutions is definitely not a new one, it is used by some OS, and I think it might be the easiest and most elegant one. E.g ‘picture.png’ becomes ‘picture-1.png’ and then ‘picture-2.png’.
Any other options you can think of ? Please comment and let me know 😉
Meanwhile we will go with Option 2.
Next question is:
Based on what should we increment this number ?
Some of the solutions I have seen before is based on counting number of files, and then incrementing count by one. This makes sense, but unfortunately this won’t work. 😉
Why?
Lets say I have files ‘a-1.jpg’, ‘a-2.jpg’,a-3.jpg’. Count is 3 here. Now Imagine I have deleted file ‘a-2.jpg’, and I want to add a new file. So current Count is 2 (a-1 and a-3), so my next filename based on logic should be a-3 (we increment count by 1 -> 2+1), but it is already taken.
Here is the strategy that I think might work:
Lets say we again have 3 files – ‘a-1.jpg’, ‘a-2.jpg’,a-3.jpg’. We will take the numbers of those files (1,2,3) find the largest and increment it by one. So if we apply to it to previous use-case – we will delete ‘a-2’, but largest number is still 3, so new filename will be ‘a-4’.
What do you think ?
I know that most of you are code-maniacs and want to dig into code right away, so I will shut up now and show you some code I have built to do exactly this 🙂
# Function generates uniq file name from the String passed to it # based on extension and basename # # @param [String] new_file_name desired file_name # # @return [String] generated file name def self.generate_random_filename(filename) ext = File.extname(filename) name = File.basename(filename, ext) related_file_indexes = [] @file_list.select do |file| if File.basename(file).include?(name) && File.extname(file) == ext related_file_indexes << file.split("-").last.to_i end end return name + '-' + (related_file_indexes.max + 1).to_s + ext end
Short Explanation:
1. I take the file lets say ‘a.jpg’, and divide it into ‘a’ as a name and ‘.jpg’ as an extension.
2. I have a @file_list array that is pre-populated with filenames in the directory lets say @file_list is [‘a-1.jpg’,’a-3.jpg’, ‘a.jpg’,’a-1.png’] .
3. I check if filename in @file_list consist of ‘a’ and with extension ‘.jpg’, thus I get 3 files [‘a-1.jpg’,’a.jpg’,’a-3.jpg’]
4. I have another array ‘related_file_indexes’, that splits every element of an array by last ‘-‘ and returns the number which gives me [‘1′,’3’]
5. I built filename with name, max of 1 and 3 which is 3+1=4 and append extension so my output will be ‘a-4.jpg‘
Full version of this code with unit tests can be found here .
Once again, all suggestions and recommendation are more than welcome in the comments to this post 😉
Have a wonderful day!
Anatoly
Thanks for installing the Bottom of every post plugin by Corey Salzano. Contact me if you need custom WordPress plugins or website design.
Tempfile.new or SecureRandom.hex
If you really want to keep the name-I convention, then keep a simple text file with a counter and RENAME a temporary file to name-I. rename is an atomic operation.
Hey Nop,
Both Tempfile.new and SecureRandom.hex – definitely work if you want to create brand new random file.
The use case I was showing here was when you are having file ‘test’ and you are copying it to the directory where it already exists, I don’t think you would expect to have file name ’12sdsdsds’ (or other hex string) after copying file ‘test’ 🙂
Having a simple text file with a counter – would work as well – very good suggestion!
As well as having a settings db table that keeps that kind of stuff.
I personally think that Reading a file every time you want to rename it is too expensive.
Thanks a lot for your suggestions!
Anatoly
Hi I want to rename so that if Text1999.pdf already exists then the next file created (Via rename) is saved as Text1999b.pdf instead of overwriting Text1999.pdf.
#!/usr/bin/ruby
require ‘fileutils’
list = Dir.glob(‘*.pdf’)
list.each do |src|
b = /^(?[a-z-]+).*_(?[\d()]+)/i.match(src)
c = b[1]+b[2]+”.pdf”
def myRename (c)
counter = 0
if
File.file?(c)
[suffix = (“a”..”z”).to_a
d = b[1]+b[2]+suffix[counter]+”.pdf”
counter = counter +1 #increment
File.rename(src,d)
]
else[
File.rename(src, c)
]
end
end
end
Very good use case Paynito!