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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
# 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
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!