Python find similar files in given folder - python

I'm trying to write a function which will find similar files by a name (song.mp3, song1.mp3, (1)song.mp3) in a specified folder. What I have by now:
def print_duplicates(source):
files_list = []
new_list = []
for dirpath, dirnames, filenames in os.walk(source):
for fname in filenames:
if ('\w*' + fname + '\w*') in files_list:
new_list.append(os.path.join(dirpath, fname))
else:
files_list.append(fname)
for a in new_list:
print(a)
If the filename wasn't before in files_list it will be added, if it was than it will be added to new_list with its path. This way I have list of 'duplicate' files. However it's not working, the new_list remains empty.
Could you correct my mistakes? Which part of my code is wrong?

If you want to use regex in your code, you need to use re module.
So change this line,
if ('\w*' + fname + '\w*') in files_list:
to,
if re.search(r'\w*' + fname + r'\w*', files_list):
which is exactly same as,
if fname in file_list:
because \w* means zero or more word characters. And I think you want to use word boundaries.
if re.search(r'\b' + fname + r'\b', files_list):

Related

str.replace and os.rename doesn't work with file names containing "/"

I want to replace a single character on a bunch of file names with another character. However, when I want to replace files with / to another character, it doesn't work. It does work with other characters I want to replace, such as -.
The python file is on the directory path, and I 'cd' to the directory path to run the program.
I expect that every / on a file name would be replaced with a _. Like this: file/1.txt to file_1.txt.
However, the files stay the same. Like this: file/1.txt to file/1.txt.
I use this code:
# Replace a character of a name file with another charater
from importlib.metadata import files
import os
counter = 0
path = r"/Users/user/test"
char = input('Enter character or string you want to replace:')
repl = input('Enter character or string you want this to be replaced with:')
files = []
# Loop, doesn't work with "/" for some reason
for file_name in os.listdir(path):
if char in file_name :
old_name = file_name
new_name = file_name.replace(char, repl)
counter += 1
files.append(new_name)
os.rename(old_name, new_name)
print(counter)
print(files)
print("Done! Check your files")
As an alternative, I deleted the variables char and repl and instead used this in the for loop, but it still doesn't work:
for file_name in os.listdir(path):
if "/" in file_name :
old_name = file_name
new_name = file_name.replace("/", "_")
counter += 1
I'd use pathlib which is much more clean and convenient that using os module.
from pathlib import Path
base_folder = Path(r"/Users/user/test")
old_names = [f for f in base_folder.glob('*') if f.is_file()]
new_names = [f'{f.parts[-2]}_{f.name}' for f in old_names]
for o_name, n_name in zip(old_names, new_names):
o_name.with_name(n_name)
You first create a list of file paths for all the files in the folder.
Then the second list comprehension goes through a list of files and for each file grabs its parent folder name and the file name and combines them in an f-string with _ in between.
Now, finally you go through the old and new file names and assign a new name to each of the original files.
EDIT:
If you want to exclude hidden files you need to amend the first comprehension to exclude files that start with a .:
old_names = [f for f in base_folder.glob('*.txt')
if not any(part.startswith('.') for part in f.parts) and f.is_file()]
I managed to solve it with this.
# Replace a character of a file with another charater
from importlib.metadata import files
import os
counter = 0
path=input("Enter path: ")
char=input('Enter character or string you want to replace:')
repl=input('Enter character or string you want this to be replaced with:')
files=[]
# Ignore hidden files
def listdir_nohidden(path):
for f in os.listdir(path):
if not f.startswith('.'):
yield f
# Loop, instead of "/" use ":"
for file_name in listdir_nohidden(path):
if char in file_name :
old_name=file_name
new_name=file_name.replace(char,repl)
counter+=1
files.append(new_name)
os.rename(old_name,new_name)
print(counter)
print(files)
print("Done! Check your files")
Files change like this: test/1.txt --> test_1.txt
The problem was that for some reason, when I debug with print(file_name), files with "/" show ":" instead. The other problem, were the hidden files, which I solved above.

Python (Filename + Sha1) generator

I have a problem with my algorithm apparently it skips a lot of sha1 hashes when executing.
No problem with the filename, but im having problem with having this output:
filename+sha1\n
For every each of them. I can guess it`s because of os.walk in some way but im not that expert ATM.
txt = open('list','w')
for dirpath, dirnames, filenames in os.walk(dir_path):
text = str(filenames)
for tag in ("[", "]", " ","'"):
text = text.replace(tag, '')
text = str(text.replace(',','\n'))
for i in filenames:
m = hashlib.sha1(str(text).encode('utf-8')).hexdigest()
txt.write(text+" "+str(m)+"\n")
txt = txt.close()
Thanks
What looks like a potential issue is that you are converting filenames, which is a list of each individual file in the current folder, into a string and then performing replacements on that list. I assume what you intended to do instead was replace within each filename string those special tags. Try the below.
txt = open('list','w')
for dirpath, dirnames, filenames in os.walk(dir_path):
for text in filenames:
text = re.sub('[\[\]," "]',"",text)
m = hashlib.sha1(str(text).encode('utf-8')).hexdigest()
txt.write(text+" "+str(m)+"\n")
txt = txt.close()
As requested, if you dont' want to use re, just do what you had originally done:
text = 'fjkla[] k,, a,[,]dd,]'
for badchar in '[]," "]':
text = text.replace(badchar,"")
Change:
txt = open('list','w')
to:
txt = open('list','a')
You are using "w" which overwrites any previous content. You need "a", which appends to the existing file without overwriting.

Iterate over 2 files in each folder and compare them

I compare two text files and print out the results to a 3rd file. I am trying to make it so the script i'm running would iterate over all of the folders that have two text files in them, in the CWD of the script.
What i have so far:
import os
import glob
path = './'
for infile in glob.glob( os.path.join(path, '*.*') ):
print('current file is: ' + infile)
with open (f1+'.txt', 'r') as fin1, open(f2+'.txt', 'r') as fin2:
Would this be a good way to start the iteration process?
It's not the most clear code but it gets the job done. However, i'm pretty sure i need to take the logic out of the read / write methods but i'm not sure where to start.
What i'm basically trying to do is have a script iterate over all of the folders in its CWD, open each folder, compare the two text files inside, write a 3rd text file to the same folder, then move on to the next.
Another method i have tried is as follows:
import os
rootDir = 'C:\\Python27\\test'
for dirName, subdirList, fileList in os.walk(rootDir):
print('Found directory: %s' % dirName)
for fname in fileList:
print('\t%s' % fname)
And this outputs the following (to give you a better example of the file structure:
Found directory: C:\Python27\test
test.py
Found directory: C:\Python27\test\asdd
asd1.txt
asd2.txt
Found directory: C:\Python27\test\chro
ch1.txt
ch2.txt
Found directory: C:\Python27\test\hway
hw1.txt
hw2.txt
Would it be wise to put the compare logic under the for fname in fileList? How do i make sure it compares the two text files inside the specific folder and not with other fnames in the fileList?
This is the full code that i am trying to add this functionality into. I appologize for the Frankenstein nature of it but i am still working on a refined version but it does not work yet.
from collections import defaultdict
from operator import itemgetter
from itertools import groupby
from collections import deque
import os
class avs_auto:
def load_and_compare(self, input_file1, input_file2, output_file1, output_file2, result_file):
self.load(input_file1, input_file2, output_file1, output_file2)
self.compare(output_file1, output_file2)
self.final(result_file)
def load(self, fileIn1, fileIn2, fileOut1, fileOut2):
with open(fileIn1+'.txt') as fin1, open(fileIn2+'.txt') as fin2:
frame_rects = defaultdict(list)
for row in (map(str, line.split()) for line in fin1):
id, frame, rect = row[0], row[2], [row[3],row[4],row[5],row[6]]
frame_rects[frame].append(id)
frame_rects[frame].append(rect)
frame_rects2 = defaultdict(list)
for row in (map(str, line.split()) for line in fin2):
id, frame, rect = row[0], row[2], [row[3],row[4],row[5],row[6]]
frame_rects2[frame].append(id)
frame_rects2[frame].append(rect)
with open(fileOut1+'.txt', 'w') as fout1, open(fileOut2+'.txt', 'w') as fout2:
for frame, rects in sorted(frame_rects.iteritems()):
fout1.write('{{{}:{}}}\n'.format(frame, rects))
for frame, rects in sorted(frame_rects2.iteritems()):
fout2.write('{{{}:{}}}\n'.format(frame, rects))
def compare(self, fileOut1, fileOut2):
with open(fileOut1+'.txt', 'r') as fin1:
with open(fileOut2+'.txt', 'r') as fin2:
lines1 = fin1.readlines()
lines2 = fin2.readlines()
diff_lines = [l.strip() for l in lines1 if l not in lines2]
diffs = defaultdict(list)
with open(fileOut1+'x'+fileOut2+'.txt', 'w') as result_file:
for line in diff_lines:
d = eval(line)
for k in d:
list_ids = d[k]
for i in range(0, len(d[k]), 2):
diffs[d[k][i]].append(k)
for id_ in diffs:
diffs[id_].sort()
for k, g in groupby(enumerate(diffs[id_]), lambda (i, x): i - x):
group = map(itemgetter(1), g)
result_file.write('{0} {1} {2}\n'.format(id_, group[0], group[-1]))
def final(self, result_file):
with open(result_file+'.txt', 'r') as fin:
lines = (line.split() for line in fin)
for k, g in groupby(lines, itemgetter(0)):
fst = next(g)
lst = next(iter(deque(g, 1)), fst)
with open('final/{}.avs'.format(k), 'w') as fout:
fout.write('video0=ImageSource("old\%06d.jpeg", {}-3, {}+3, 15)\n'.format(fst[1], lst[2]))
fout.write('video1=ImageSource("new\%06d.jpeg", {}-3, {}+3, 15)\n'.format(fst[1], lst[2]))
fout.write('video0=BilinearResize(video0,640,480)\n')
fout.write('video1=BilinearResize(video1,640,480)\n')
fout.write('StackHorizontal(video0,video1)\n')
fout.write('Subtitle("ID: {}", font="arial", size=30, align=8)'.format(k))
using the load_and_compare() function, i define two input text files, two output text files, a file for the comparison results and a final phase that writes many files for all of the differences.
What i am trying to do is have this whole class run on the current working directory and go through every sub folder, compare the two text files, and write everything into the same folder, specifically the final() results.
You can indeed use os.walk(), since that already separates the directories from the files. You only need the directories it returns, because that's where you're looking for your 2 specific files.
You could also use os.listdir() but that returns directories as well files in the same list, so you would have to check for directories yourself.
Either way, once you have the directories, you iterate over them (for subdir in dirnames) and join the various path components you have: The dirpath, the subdir name that you got from iterating over the list and your filename.
Assuming there are also some directories that don't have the specific 2 files, it's a good idea to wrap the open() calls in a try..except block and thus ignore the directories where one of the files (or both of them) doesn't exist.
Finally, if you used os.walk(), you can easily choose if you only want to go into directories one level deep or walk the whole depth of the tree. In the former case, you just clear the dirnames list by dirnames[:] = []. Note that dirnames = [] wouldn't work, since that would just create a new empty list and put that reference into the variable instead of clearing the old list.
Replace the print("do something ...") with your program logic.
#!/usr/bin/env python
import errno
import os
f1 = "test1"
f2 = "test2"
path = "."
for dirpath, dirnames, _ in os.walk(path):
for subdir in dirnames:
filepath1, filepath2 = [os.path.join(dirpath, subdir, f + ".txt") for f in f1, f2]
try:
with open(filepath1, 'r') as fin1, open(filepath2, 'r') as fin2:
print("do something with " + str(fin1) + " and " + str(fin2))
except IOError as e:
# ignore directiories that don't contain the 2 files
if e.errno != errno.ENOENT:
# reraise exception if different from "file or directory doesn't exist"
raise
# comment the next line out if you want to traverse all subsubdirectories
dirnames[:] = []
Edit:
Based on your comments, I hope I understand your question better now.
Try the following code snippet instead. The overall structure stays the same, only now I'm using the returned filenames of os.walk(). Unfortunately, that would also make it harder to do something like "go only into the subdirectories 1 level deep", so I hope walking the tree recursively is fine with you. If not, I'll have to add a little code to later.
#!/usr/bin/env python
import fnmatch
import os
filter_pattern = "*.txt"
path = "."
for dirpath, dirnames, filenames in os.walk(path):
# comment this out if you don't want to filter
filenames = [fn for fn in filenames if fnmatch.fnmatch(fn, filter_pattern)]
if len(filenames) == 2:
# comment this out if you don't want the 2 filenames to be sorted
filenames.sort(key=str.lower)
filepath1, filepath2 = [os.path.join(dirpath, fn) for fn in filenames]
with open(filepath1, 'r') as fin1, open(filepath2, 'r') as fin2:
print("do something with " + str(fin1) + " and " + str(fin2))
I'm still not really sure what your program logic does, so you will have to interface the two yourself.
However, I noticed that you're adding the ".txt" extension to the file name explicitly all over your code, so depending on how you are going to use the snippet, you might or might not need to remove the ".txt" extension first before handing the filenames over. That would be achieved by inserting the following line after or before the sort:
filenames = [os.path.splitext(fn)[0] for fn in filenames]
Also, I still don't understand why you're using eval(). Do the text files contain python code? In any case, eval() should be avoided and be replaced by code that's more specific to the task at hand.
If it's a list of comma separated strings, use line.split(",") instead.
If there might be whitespace before or after the comma, use [word.strip() for word in line.split(",")] instead.
If it's a list of comma separated integers, use [int(num) for num in line.split(",")] instead - for floats it works analogously.
etc.

filter directory in python

I am trying to get filtered list of all Text and Python file, like below
from walkdir import filtered_walk, dir_paths, all_paths, file_paths
vdir=raw_input ("enter director :")
files = file_paths(filtered_walk(vdir, depth=0,included_files=['*.py', '*.txt']))
I want to:
know the total number of files found in given directory
I have tried options like : Number_of_files= len (files) or for n in files n=n+1 but all are failing as "files" is something called "generator" Object which I searched on python docs but couldn't make use of it
I also want to find a string e.g. "import sys" in the list of files found in above and store the file names having my search string in new file called "found.txt"
I believe this does what you want, if I misunderstood your specification, please let me know after you give this a test. I've hardcoded the directory searchdir, so you'll have to prompt for it.
import os
searchdir = r'C:\blabla'
searchstring = 'import sys'
def found_in_file(fname, searchstring):
with open(fname) as infp:
for line in infp:
if searchstring in line:
return True
return False
with open('found.txt', 'w') as outfp:
count = 0
search_count = 0
for root, dirs, files in os.walk(searchdir):
for name in files:
(base, ext) = os.path.splitext(name)
if ext in ('.txt', '.py'):
count += 1
full_name = os.path.join(root, name)
if found_in_file(full_name, searchstring):
outfp.write(full_name + '\n')
search_count += 1
print 'total number of files found %d' % count
print 'number of files with search string %d' % search_count
Using with to open the file will also close the file automatically for you later.
A python generator is a special kind of iterator. It yields one item after the other, without knowing in advance how much items there are. You only can know it at the end.
It should be ok, though, to do
n = 0
for item in files:
n += 1
do_something_with(items)
print "I had", n, "items."
You can think of a generator (or generally, an iterator) as a list that gives you one item at a time. (NO, it is not a list). So, you cannot count how much items it will give you unless you go through them all, because you have to take them one by one. (This is just a basic idea, now you should be able to understand the docs, and I'm sure there are lots of questions here about them too).
Now, for your case, you used a not-so-wrong approach:
count = 0
for filename in files:
count += 1
What you were doing wrong was taking f and incrementing, but f here is the filename! Incrementing makes no sense, and an Exception too.
Once you have these filenames, you have to open each individual file, read it, search for your string and return the filename.
def contains(filename, match):
with open(filename, 'r') as f:
for line in f:
if f.find(match) != -1:
return True
return False
match_files = []
for filename in files:
if contains(filename, "import sys"):
match_file.append(filename)
# or a one-liner:
match_files = [f for f in files if contains(f, "import sys")]
Now, as an example of a generator (don't read this before you read the docs):
def matching(filenames):
for filename in files:
if contains(filename, "import sys"):
# feed the names one by one, you are not storing them in a list
yield filename
# usage:
for f in matching(files):
do_something_with_the_files_that_match_without_storing_them_all_in_a_list()
You should try os.walk
import os
dir = raw_input("Enter Dir:")
files = [file for path, dirname, filenames in os.walk(dir) for file in filenames if file[-3:] in [".py", ".txt"]]
nfiles = len(files)
print nfiles
For searching for a string in a file look at Search for string in txt file Python
Combining both these your code would be something like
import os
import mmap
dir = raw_input("Enter Dir:")
print "Directory %s" %(dir)
search_str = "import sys"
count = 0
search_count = 0
write_file = open("found.txt", "w")
for dirpath, dirnames, filenames in os.walk(dir):
for file in filenames:
if file.split(".")[-1] in ["py", "txt"]:
count += 1
print dirpath, file
f = open(dirpath+"/"+file)
# print f.read()
if search_str in f.read():
search_count += 1
write_file.write(dirpath+"/"+file)
write_file.close()
print "Number of files: %s" %(count)
print "Number of files containing string: %s" %(search_count)

get full path name using list comprehension in python

can I do something like this(actually the it doesn't work)
flist = [dirpath + f for f for fnames for dirpath, dirnames, fnames in os.walk('/home/user')]
thanks!
fnames doesn't exist yet. Swap the loops.
flist = [dirpath + f for dirpath, dirnames, fnames in os.walk('/home/user') for f in fnames]
Personally I'd write it as a generator:
def filetree(top):
for dirpath, dirnames, fnames in os.walk(top):
for fname in fnames:
yield os.path.join(dirpath, fname)
Then you can either use it in a loop:
for name in filetree('/home/user'):
do_something_with(name)
or slurp it into a list:
flist = list(filetree('/home/user'))
flist = [os.path.join(pdir,f) for pdir, dirs, files in os.walk('/home/user') for f in files]
(os.path.join should be used instead of string concatenation to handle OS-specific separators and idiosyncrasies)
However, as several have already pointed out, multi-level list comprehension is not very readable and easy to get wrong.
Assuming you really do want to have the results in a list:
flist = []
for root, dirs, files in os.walk(root_dir):
flist.extend(os.path.join(root, f) for f in files)
# to support python <2.4, use flist.extend([...])
If you're simply using flist as an intermediate storage to iterate through, you might be better off using a generator as shown in John's answer.
Using map:
map(lambda data: map(lambda file: data[0] + '\\' + file, data[2]), os.walk('/home/user'))
OR:
map(lambda data: map(lambda file: os.path.join(data[0], file), data[2]), os.walk('/home/user'))
path = '/home/user/' # keep trailing '/'
flist = [path+name for name in os.listdir(path)]

Categories

Resources