Rename a directory with a filename inside with Python - python

I am trying to rename several directories with the name of the first file inside them.
I am trying to:
List the files inside a folder.
Identify the directories.
For each directory, access it, grab the name of the first file inside and rename the directory with such name.
This is what I got so far but it is not working. I know the code is wrong but before fixing the code I would like to know if the logic is right. Can anyone help please?
import os
for (root, dirs, files) in os.walk('.'):
print(f'Found directory: {dirpath}')
dirlist = []
for d_idx, d in enumerate(dirlist):
print(d)
filelist = []
for f_idex, f in enumerate(filelist):
files.append(f)[1]
print(f)
os.rename(d, f)
Thank you!

There are a few problems in your code:
You are renaming directories as you iterate them with os.walk. This is not a good idea, os.walk gives you a generator, meaning it creates elements as you iterate them, so renaming things within the loop will confuse it.
Both for d_idx, d in enumerate(dirlist): and for f_idex, f in enumerate(filelist): iterate over variables that are declared to be empty lists in the line before, so those loops don't do anything. Also, within the second one, files.append(f) would append f to the list files, but the [1] at the end means "get the second element (remeber Python indexing is 0-based) of the value returned by the append function" - but append does not return anything (it modifies the list, not returns a new list), so that would fail (and you are not using the value read by [1] anyway, so it would not do anything).
In os.rename(d, f), first, since the loops before do not ever run, d and f will not have a value, but also, assuming both d and f came from dirs and files, they would be given as paths relative to their parents, not to your current directory (.), so the renaming would fail.
This code should work as you want:
import os
# List of paths to rename
renames = []
# Walk current dir
for (root, dirs, files) in os.walk('.'):
# Skip this dir (cannot rename current directory)
if root == '.': continue
# Add renaming to list
renames.append((root, files[0]))
# Iterate renaming list in reverse order so deepest dirs are renamed first
for root, new_name in reversed(renames):
# Make new full dir name (relative to current directory)
new_full_name = os.path.join(os.path.dirname(root), new_name)
# Rename
os.rename(root, new_full_name)

Related

Return number of folders in directory and subdirectory

I have a directory similar the example down below which contains the following folders:
C:\Users\xx\Desktop\New folder\New folder\New folder\QGIS
C:\Users\xx\Desktop\New folder\New folder\New folder (2)\1- QGIS
C:\Users\xx\Desktop\New folder\New folder\New folder (4)\1.0 QGIS
C:\Users\xx\Desktop\New folder\New folder\QGIS
I wish to find how many folders with their names ends in QGIS and their path.
My current script is down below. It successfully gives me the path of all folders name ends in QGIS but the script counts only the folders with name "QGIS" only and doesnt count "1.0 QGIS" or "1- QGIS". What am I missing?
import os
rootfolder = r'C:\Users\xx\Desktop\New folder'
isfile = os.path.isfile
join = os.path.join
i=0
with open("folderpath.txt", 'w') as f:
for root, dirs, files in os.walk(rootfolder, topdown=False):
i+= dirs.count('*QGIS')
for name in dirs:
if name.endswith("QGIS"):
f.write(os.path.join(root, name)+'\n')
f.write(str((sum(dirs.count('QGIS') for _, dirs, _ in os.walk(rootfolder)))))
The list.count method does not support any concept of a wildcard -- it just looks for how many elements are equal to the value that is given as an argument. So your line
i+= dirs.count('*QGIS')
is looking for directories which are literally called *QGIS, rather than directories that end with QGIS.
The fix here should be easy because the code is already successfully printing out the correct paths; it is just not counting them correctly. So all that you need to do is to remove the above statement, and instead just add 1 in the place where you print out each path, which is already subject to the correct if condition inside the loop over directory names.
for root, dirs, files in os.walk(rootfolder, topdown=False):
for name in dirs:
if name.endswith("QGIS"):
f.write(os.path.join(root, name)+'\n')
i += 1
You already correctly initialise i=0 before the start of the loop.
At the end, just do:
print(i)
and get rid of that expression involving sum where you walk through all the directories a second time.
import os
print( len( list( filter(None, map(lambda x: x[0] if x[0].endswith('QGIS') else None,os.walk('.'))))))
A shorter form, but not too readable ;)
The "map" goes through the results of os.walk, returns the folder name if it ends with 'QGIS' and None if not.
The "filter" returns every value from map's results which differ from value None.
The "list" is needed, because both map and filter are returning an iterator object, which has no length, but the "list" has.

Exclude certain directories in Python3

I've been porting (very simply) a Python script from Windows to Linux (directory changes mostly), and I want to add a few new features to it.
The script is used to update mods on a game server. All mods are located in ShooterGame/Content/Mods/. Some mods are included by default (TheCenter and 11111111) - every other mod is located in the same folder as the default ones, but the names consist of random numbers.
I've been trying to exclude the 2 default directories and then build a list of contents of the ShooterGame/Content/Mods/ folder, but I've failed to do so.
This is the code that I've tried to use to exclude just the TheCenter folder:
def build_list_of_mods(self):
"""
Build a list of all installed mods by grabbing all directory names from the mod folder
:return:
"""
exclude = ["TheCenter"]
if not os.path.isdir(os.path.join(self.working_dir, "ShooterGame/Content/Mods/")):
return
for curdir, dirs, files in os.walk(os.path.join(self.working_dir, "ShooterGame/Content/Mods/")):
for d in dirs:
dirs[:] = [d for d in dirs if d not in exclude]
self.installed_mods.append(d)
break
It doesn't work, sadly. Have I missed something or just done everything wrong?
Try adding topdown=True to the os.walk() function like this:
for curdir, dirs, files in os.walk(os.path.join(self.working_dir, "ShooterGame/Content/Mods/"), topdown=True):
Plus I cannot try it but maybe dirs[:] should be outside of the for-loop, as the documentation says:
When topdown is true, the caller can modify the dirnames list in-place (e.g., via del or slice assignment), and walk will only recurse into the subdirectories whose names remain in dirnames;
I'm assuming you want self.installed_mods to contain the values of dirs without the values of exclude.
You could simply call dirs.remove() with the values of exclude and then append the content of dirs to self.installed_mods.
Or in a shorter way: self.installed_mods.extend([dir for dir in dirs if dir not in exclude]).

Searching recursively for files to add to list, but if one type of file is found ignore other type

How can I search recursively for files to add to a list, but if one type of file is found ignore another type?
Here is my current code:
import os
import fnmatch
rootDir = "//path/to/top/level/directory"
ignore = ['ignoreThisDir','ignoreThisToo']
fileList = []
for dirpath, dirnames, files in os.walk(rootDir):
for idir in ignore:
if idir in dirnames:
dirnames.remove(idir)
for name in files:
if fnmatch.fnmatch(name, 'A.csv') or fnmatch.fnmatch(name, 'B.csv'):
fileList.append(os.path.join(dirpath, name))
Currently this code is partially working for me. It takes a top level directory and searches down recursively through the directory tree creating a list of directories and files within, removing the directories that I don't want the code to os.walk through.
But there is one extra step I can't work out.
If B.csv exists in a directory, I only want to append it, and not A.csv. But if B.csv is not found then I do want to append A.csv to my list of files.
My current code appends both.
If B.csv exists in a directory, I only want to append it and not A.csv. But if B.csv is not found then I do want to append A.csv to my list of files.
There are two ways to do this.
First, you can make two passes through the directory: first search for B.csv, then, only if it wasn't found, search for A.csv. Like this:
for name in files:
if fnmatch.fnmatch(name, 'B.csv'):
fileList.append(os.path.join(dirpath, name))
break
else:
for name in files:
if fnmatch.fnmatch(name, 'A.csv'):
fileList.append(os.path.join(dirpath, name))
break
(If you've never seen a for…else before, the else part triggers if you finished the for loop without hitting a break—in other words, if you didn't find B.csv anywhere.)
Alternatively, you can remember that you found A.csv, but not add it until you know that you haven't found B.csv:
a = b = None
for name in files:
if fnmatch.fnmatch(name, 'A.csv'):
a = name
elif fnmatch.fnmatch(name, 'B.csv'):
b = name
fileList.append(os.path.join(dirpath, name))
if a is not None and b is None:
fileList.append(os.path.join(dirpath, a))
You can also combine the two approaches—break as soon as you find B.csv, and use a for…else followed by just if a is not None:.
As a side note, you don't need fnmatch if all you're doing is checking for an exact match. It's only necessary when you're matching glob patterns, like '*.csv' or the list. So you can simplify this quite a bit:
files = set(files)
if 'B.csv' in files:
fileList.append(os.path.join(dirpath, 'B.csv'))
elif 'A.csv' in files:
fileList.append(os.path.join(dirpath, 'A.csv'))

Efficiently removing subdirectories in dirnames from os.walk

On a mac in python 2.7 when walking through directories using os.walk my script goes through 'apps' i.e. appname.app, since those are really just directories of themselves. Well later on in processing I am hitting errors when going through them. I don't want to go through them anyways so for my purposes it would be best just to ignore those types of 'directories'.
So this is my current solution:
for root, subdirs, files in os.walk(directory, True):
for subdir in subdirs:
if '.' in subdir:
subdirs.remove(subdir)
#do more stuff
As you can see, the second for loop will run for every iteration of subdirs, which is unnecessary since the first pass removes everything I want to remove anyways.
There must be a more efficient way to do this. Any ideas?
You can do something like this (assuming you want to ignore directories containing '.'):
subdirs[:] = [d for d in subdirs if '.' not in d]
The slice assignment (rather than just subdirs = ...) is necessary because you need to modify the same list that os.walk is using, not create a new one.
Note that your original code is incorrect because you modify the list while iterating over it, which is not allowed.
Perhaps this example from the Python docs for os.walk will be helpful. It works from the bottom up (deleting).
# Delete everything reachable from the directory named in "top",
# assuming there are no symbolic links.
# CAUTION: This is dangerous! For example, if top == '/', it
# could delete all your disk files.
import os
for root, dirs, files in os.walk(top, topdown=False):
for name in files:
os.remove(os.path.join(root, name))
for name in dirs:
os.rmdir(os.path.join(root, name))
I am a bit confused about your goal, are you trying to remove a directory subtree and are encountering errors, or are you trying to walk a tree and just trying to list simple file names (excluding directory names)?
I think all that is required is to remove the directory before iterating over it:
for root, subdirs, files in os.walk(directory, True):
if '.' in subdirs:
subdirs.remove('.')
for subdir in subdirs:
#do more stuff

WxPython - building a directory tree based on file availability

I do atomistic modelling, and use Python to analyze simulation results. To simplify work with a whole bunch of Python scripts used for different tasks, I decided to write simple GUI to run scripts from it.
I have a (rather complex) directory structure beginning from some root (say ~/calc), and I want to populate wx.TreeCtrl control with directories containing calculation results preserving their structure. The folder contains the results if it contains a file with .EXT extension. What i try to do is walk through dirs from root and in each dir check whether it contains .EXT file. When such dir is reached, add it and its ancestors to the tree:
def buildTree(self, rootdir):
root = rootdir
r = len(rootdir.split('/'))
ids = {root : self.CalcTree.AddRoot(root)}
for (dirpath, dirnames, filenames) in os.walk(root):
for dirname in dirnames:
fullpath = os.path.join(dirpath, dirname)
if sum([s.find('.EXT') for s in filenames]) > -1 * len(filenames):
ancdirs = fullpath.split('/')[r:]
ad = rootdir
for ancdir in ancdirs:
d = os.path.join(ad, ancdir)
ids[d] = self.CalcTree.AppendItem(ids[ad], ancdir)
ad = d
But this code ends up with many second-level nodes with the same name, and that's definitely not what I want. So I somehow need to see if the node is already added to the tree, and in positive case add new node to the existing one, but I do not understand how this could be done. Could you please give me a hint?
Besides, the code contains 2 dirty hacks I'd like to get rid of:
I get the list of ancestor dirs with splitting the full path in \
positions, and this is Linux-specific;
I find if .EXT file is in the directory by trying to find the extension in the strings from filenames list, taking in account that s.find returns -1 if the substring is not found.
Is there a way to make these chunks of code more readable?
First of all the hacks:
To get the path seperator for whatever os your using you can use os.sep.
Use str.endswith() and use the fact that in Python the empty list [] evaluates to False:
if [ file for file in filenames if file.endswith('.EXT') ]:
In terms of getting them all nicely nested you're best off doing it recursively. So the pseudocode would look something like the following. Please note this is just provided to give you an idea of how to do it, don't expect it to work as it is!
def buildTree(self, rootdir):
rootId = self.CalcTree.AddRoot(root)
self.buildTreeRecursion(rootdir, rootId)
def buildTreeRecursion(self, dir, parentId)
# Iterate over the files in dir
for file in dirFiles:
id = self.CalcTree.AppendItem(parentId, file)
if file is a directory:
self.buildTreeRecursion(file, id)
Hope this helps!

Categories