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?

\$\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')
\$\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\$
    – xtian
    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.

\$\endgroup\$

Your Answer

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

Not the answer you're looking for? Browse other questions tagged or ask your own question.