【问题标题】:Python - Way to recursively find and replace string in text filesPython - 在文本文件中递归查找和替换字符串的方法
【发布时间】:2011-05-11 11:44:23
【问题描述】:

我想递归搜索带有文本文件子目录的目录,并用多行字符串的内容替换文件中每次出现的 {$replace}。用python如何实现?

[编辑]

到目前为止,我只有使用 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

【问题讨论】:

  • 如果您告诉我们您目前的情况,我们将更有可能提供帮助。
  • 为什么需要为此使用 Python?在我看来,findsed 的组合会更优雅地完成这项工作。 developmentality.wordpress.com/2010/09/07/… 基本上,find . -type f -exec sed -i '.bk' 's/search regexp/replacement string/g' {} \;(尚未测试,但我认为这是正确的语法)
  • @I82Much - 但您的解决方案需要学习 sed。如果 OP 已经了解基本 Python,并且不痴迷于学习新工具(更喜欢掌握现有工具),那么用 Python 解决任务更有意义。
  • @I82Much: c:\test 可能是原因之一(Windows)。 find /c/test -type d -path \*/.svn -prune -o -type f -name \*.php -exec sed -i 's/{\$replace}/multiline\nstring/g' {} + shell 命令接近于this Python code

标签: python


【解决方案1】:

这是一个老问题,但我想我会使用 python3.8 中的当前库提供一个更新且更简单的答案。

from pathlib import Path
import re

rootdir = Path("C:\\test")
pattern = r'REGEX for the text you want to replace'
replace = r'REGEX for what to replace it with'

for file in [ f for f in rootdir.glob("**.php") ]: #modify glob pattern as needed
  file_contents = file.read_text()
  new_file_contents = re.sub(f"{pattern}", f"{replace}", file_contents)
  file.write_text(new_file_contents)

【讨论】:

    【解决方案2】:

    用途:

    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)
    

    这应该使该功能适用​​于整个文件夹。

    【讨论】:

      【解决方案3】:

      如何使用:

      clean = ''.join([e for e in text if e != 'string'])
      

      【讨论】:

        【解决方案4】:

        对于那些使用 Python 3.5+ 的用户,您现在可以通过使用 **recursive 标志递归地使用 glob

        这是一个将所有.txt 文件的hello 替换为world 的示例:

        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)
        

        【讨论】:

        • 对于 windows 可能会出现错误'UnicodeDecodeError: 'charmap' codec can't decode byte 0x9d in position 676628: character maps to' 这是编码错误。参考这个stackoverflow.com/a/9233174/452708
        【解决方案5】:

        多个文件字符串变化

        导入全局

        对于 glob.glob('*.txt') 中的所有文件:

        for line in open(allfiles,'r'):
            change=line.replace("old_string","new_string")
            output=open(allfiles,'w')
            output.write(change)    
        

        【讨论】:

        • 请在发布之前测试您的代码,这只会删除文件的大部分内容!不要尝试这个!
        • 正如@ThierryLathuille 所说,这个答案是错误的并且可能有害。
        【解决方案6】:

        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()
        

        【讨论】:

          【解决方案7】:

          为避免递归到.svn 目录,os.walk() 允许您就地更改dirs 列表。为了简化文件中的文本替换而不需要读取内存中的整个文件,您可以使用fileinput module。要使用文件模式过滤文件名,您可以将fnmatch module 用作suggested by @David Sulpy

          #!/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")
          

          【讨论】:

            【解决方案8】:

            这是我的代码(我认为与上面的代码相同,但我将其包括在内以防万一它有细微的不同):

            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"),但它们也不会打印出来。

            【讨论】:

              【解决方案9】:

              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")
              

              【讨论】:

              • 这正是我想要的。多么棒的答案!
              • 为了避免将整个文件加载到内存中,您可以使用fileinput module
              【解决方案10】:

              查看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)
              

              上述解决方案存在缺陷,例如它实际上会打开它找到的每个文件,或者每个文件都被完全读入内存(如果你有一个 1GB 的文本文件会很糟糕),但它应该是一个很好的起点。

              如果您想要进行比查找特定字符串更复杂的查找/替换,您可能还需要查看 re module

              【讨论】:

              • 应该在s = "" 行之前with open...
              猜你喜欢
              • 1970-01-01
              • 2015-10-18
              • 2021-05-21
              • 2011-05-25
              • 2023-03-04
              • 2013-02-19
              • 2020-08-12
              • 1970-01-01
              • 2016-11-18
              相关资源
              最近更新 更多