2
\$\begingroup\$

My little Python scriptie needs to save a file to the CWD, but sometimes the file already exists.

I found this short and clever example to string concatenate a time signature to the name here on SE. But I wanted to make the file name look pretty,

import os
import fnmatch
import re
somefiles = []
myfiles = []
root = "./"
onefn = False
for item in os.listdir(root):
 if os.path.isfile(os.path.join(root,item)):
 somefiles.append(item)
for fname in somefiles:
 if fnmatch.fnmatch(fname, 'somefile(*).txt'):
 myfiles.append(fname)
 elif fname == 'somefile.txt':
 onefn = True 
if len(myfiles) > 0:
 maxnum = re.search(r'\(([0-9]+)\)', max(myfiles))
 with open('somefile(%s).txt' % (str(int(maxnum.group(1)) + 1),), 'w') as f:
 f.write("This is some text in a new file.")
elif onefn:
 with open('somefile(1).txt', 'w') as f:
 f.write("This is sommore text in a new file.")
else: 
 with open('somefile.txt', 'w') as f:
 f.write("This is enuf text in a new file.")

This is a beast, but it will write somefile(n).txt. Any recommendations to make it less beastly?

asked Apr 21, 2018 at 2:17
\$\endgroup\$
0

2 Answers 2

2
\$\begingroup\$

In Python there're list comprehensions and generator expressions, that let you build a list or a generator in a single line. Your two initial fors can be replaced with:

# I'm supposing that each item in ROOT folder that matches
# 'somefile*.txt' is a file which we are looking for
files = fnmatch.filter((f for f in os.listdir(ROOT)), 'somefile*.txt')

So now files contains file names as ['somefile.txt', 'somefile(1).txt', ...].

Now, as you pointed out, we have three options: files is empty, files contains only the element 'somefile.txt' or files contains multiple elements.

if not files: # is empty
 num = ''
elif len(files) == 1:
 num = '(1)'
else:
 # files is supposed to contain 'somefile.txt'
 files.remove('somefile.txt')
 num = '(%i)' % (int(re.search(r'\(([0-9]+)\)', max(files)).group(1))+1)

We introduce that variable num, to parametrize the suffix of somefile.txt:

with open('somefile%s.txt' % num, 'w') as f:
 f.write('some text')

So the complete script is:

import os
import re
import fnmatch
ROOT = './' # uppercase because it's a constant
# I'm supposing that each item in ROOT folder that matches
# 'somefile*.txt' is a file which we are looking for
files = fnmatch.filter((f for f in os.listdir(ROOT)), 'somefile*.txt')
if not files: # is empty
 num = ''
elif len(files) == 1:
 num = '(1)'
else:
 # files is supposed to contain 'somefile.txt'
 files.remove('somefile.txt')
 num = '(%i)' % (int(re.search(r'\(([0-9]+)\)', max(files)).group(1))+1)
with open('somefile%s.txt' % num, 'w') as f:
 f.write('some text')
answered Apr 21, 2018 at 12:12
\$\endgroup\$
1
  • \$\begingroup\$ I like it. The list comprehension for fnmatch.filter is very intuitive improvement. Good you noticed if files contains non-int name, then max(files) returns wrong name. Only one detail I might try to add, elif len(files) == 1 and fnmatch.filter((f for f in os.listdir(ROOT)), 'somefile.txt') to ensure files doesn't contain one file which could be somefile(1).txt. \$\endgroup\$ Commented Apr 21, 2018 at 14:06
4
\$\begingroup\$

One aspect I'd like to mention is sorting:

The contain does a max(myfiles), which is on the filenames.

In particular, out of "somefile(9).txt" and "somefile(10).txt", the max will be "somefile(9).txt". It will then choose "somefile(10).txt" as the new filename, overwriting its original content.

This is because 9 is larger than 1.

answered Apr 21, 2018 at 21:34
\$\endgroup\$

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.