Подтвердить что ты не робот

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
4b9b3361

Ответ 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)