Python - способ рекурсивного поиска и замены строки в текстовых файлах
Я хочу рекурсивно искать в каталоге с подкаталогами текстовых файлов и заменять каждое вхождение {$ replace} в файлах содержимым многострочной строки. Как это можно достичь с помощью python?
[EDIT]
До сих пор у меня есть рекурсивный код, использующий os.walk, чтобы получить список файлов, которые необходимо изменить.
import os
import sys
fileList = []
rootdir = "C:\\test"
for root, subFolders, files in os.walk(rootdir):
if subFolders != ".svn":
for file in files:
fileParts = file.split('.')
if len(fileParts) > 1:
if fileParts[1] == "php":
fileList.append(os.path.join(root,file))
print fileList
Ответы
Ответ 1
Отъезд os.walk:
import os
replacement = """some
multi-line string"""
for dname, dirs, files in os.walk("some_dir"):
for fname in files:
fpath = os.path.join(dname, fname)
with open(fpath) as f:
s = f.read()
s = s.replace("{$replace}", replacement)
with open(fpath, "w") as f:
f.write(s)
Вышеупомянутое решение имеет недостатки, такие как тот факт, что он открывает буквально каждый найденный файл или тот факт, что каждый файл полностью считывается в память (что было бы плохо, если бы у вас был текстовый файл 1 ГБ), но он должен быть хорошей отправной точкой.
Вы также можете захотеть изучить re module, если хотите выполнить более сложную поиск/замену, чем искать определенную строку.
Ответ 2
os.walk отлично. Тем не менее, похоже, что вам нужно подавать файлы типов файлов (которые я бы предложил, если вы собираетесь ходить по какой-либо директории). Чтобы сделать это, вы должны добавить import fnmatch
.
import os, fnmatch
def findReplace(directory, find, replace, filePattern):
for path, dirs, files in os.walk(os.path.abspath(directory)):
for filename in fnmatch.filter(files, filePattern):
filepath = os.path.join(path, filename)
with open(filepath) as f:
s = f.read()
s = s.replace(find, replace)
with open(filepath, "w") as f:
f.write(s)
Это позволяет вам сделать что-то вроде:
findReplace("some_dir", "find this", "replace with this", "*.txt")
Ответ 3
Для тех, кто использует Python 3. 5+ теперь вы можете использовать glob рекурсивно с использованием **
и recursive
флага.
Вот пример замены hello
на world
для всех файлов .txt
:
for filepath in glob.iglob('./**/*.txt', recursive=True):
with open(filepath) as file:
s = file.read()
s = s.replace('hello', 'world')
with open(filepath, "w") as file:
file.write(s)
Ответ 4
Чтобы избежать рекурсии в директории .svn
, os.walk()
позволяет вам изменить список dirs
на месте. Чтобы упростить замену текста в файле, не требуя для чтения всего файла в памяти, вы можете использовать fileinput
module. И чтобы фильтровать имена файлов с использованием шаблона файла, вы можете использовать fnmatch
module как предложенный @Дэвид Сульпи:
#!/usr/bin/env python
from __future__ import print_function
import fnmatch
import os
from fileinput import FileInput
def find_replace(topdir, file_pattern, text, replacement):
for dirpath, dirs, files in os.walk(topdir, topdown=True):
dirs[:] = [d for d in dirs if d != '.svn'] # skip .svn dirs
files = [os.path.join(dirpath, filename)
for filename in fnmatch.filter(files, file_pattern)]
for line in FileInput(files, inplace=True):
print(line.replace(text, replacement), end='')
find_replace(r"C:\test", "*.php", '{$replace}', "multiline\nreplacement")
Ответ 5
Ответ на Sulpy - хороший, но неполный. Пользователь, скорее всего, захочет ввести параметры через виджет ввода, поэтому у нас может быть нечто подобное (также неполное, но оставленное как упражнение):
import os, fnmatch
from Tkinter import *
fields = 'Folder', 'Search', 'Replace', 'FilePattern'
def fetch(entvals):
# print entvals
# print ents
entItems = entvals.items()
for entItem in entItems:
field = entItem[0]
text = entItem[1].get()
print('%s: "%s"' % (field, text))
def findReplace(entvals):
# print ents
directory = entvals.get("Folder").get()
find = entvals.get("Search").get()
replace = entvals.get("Replace").get()
filePattern = entvals.get("FilePattern").get()
for path, dirs, files in os.walk(os.path.abspath(directory)):
for filename in fnmatch.filter(files, filePattern):
# print filename
filepath = os.path.join(path, filename)
print filepath # Can be commented out -- used for confirmation
with open(filepath) as f:
s = f.read()
s = s.replace(find, replace)
with open(filepath, "w") as f:
f.write(s)
def makeform(root, fields):
entvals = {}
for field in fields:
row = Frame(root)
lab = Label(row, width=17, text=field+": ", anchor='w')
ent = Entry(row)
row.pack(side=TOP, fill=X, padx=5, pady=5)
lab.pack(side=LEFT)
ent.pack(side=RIGHT, expand=YES, fill=X)
entvals[field] = ent
# print ent
return entvals
if __name__ == '__main__':
root = Tk()
root.title("Recursive S&R")
ents = makeform(root, fields)
# print ents
root.bind('<Return>', (lambda event, e=ents: fetch(e)))
b1 = Button(root, text='Show', command=(lambda e=ents: fetch(e)))
b1.pack(side=LEFT, padx=5, pady=5)
b2 = Button(root, text='Execute', command=(lambda e=ents: findReplace(e)))
b2.pack(side=LEFT, padx=5, pady=5)
b3 = Button(root, text='Quit', command=root.quit)
b3.pack(side=LEFT, padx=5, pady=5)
root.mainloop()
Ответ 6
Здесь мой код (который, я думаю, такой же, как и выше, но я включаю его просто в случае, если в нем что-то тонко отличается):
import os, fnmatch, sys
def findReplace(directory, find, replace, filePattern):
for path, dirs, files in os.walk(os.path.abspath(directory)):
for filename in fnmatch.filter(files, filePattern):
filepath = os.path.join(path, filename)
with open(filepath) as f:
s = f.read()
s = s.replace(find, replace)
with open(filepath, "w") as f:
f.write(s)
он работает без ошибок.
НО, файл, в z:\test
не изменяется.
Я поставил печатные операторы, например print("got here")
, но они также не распечатываются.
Ответ 7
Как насчет просто использовать:
clean = ''.join([e for e in text if e != 'string'])
Ответ 8
Использование:
pip3 install manip
Это позволяет вам использовать декоратор для создания чего-то вроде:
@manip(at='.php$', recursive=True) # to apply to subfolders
def replace_on_php(text, find, replacement):
return text.replace(find, replacement)
Теперь в вашем приглашении вы сможете позвонить
replace_on_php('explode', 'myCustomExplode', path='./myPhPFiles', modify=True)
и это должно заставить функцию применить себя на всю папку.
Ответ 9
Изменение строки нескольких файлов
import glob
для allfiles в glob.glob('*. txt'):
for line in open(allfiles,'r'):
change=line.replace("old_string","new_string")
output=open(allfiles,'w')
output.write(change)