使用Python递归地替换指定目录及其子目录中的文件字符串?

15 投票
5 回答
26578 浏览
提问于 2025-04-15 15:13

你怎么能在一个文件里,把某个字符串替换成指定的内容,并且这个操作要在一个文件夹及其子文件夹里递归进行呢?

伪代码:

import os
import re
from os.path import walk
for root, dirs, files in os.walk("/home/noa/Desktop/codes"):
        for name in dirs:
                re.search("dbname=noa user=noa", "dbname=masi user=masi")
                   // I am trying to replace here a given match in a file

5 个回答

4

当然,如果你只是想完成这个任务,而不想写代码的话,可以使用 find 和 xargs 命令:

find /home/noa/Desktop/codes -type f -print0 | \
xargs -0 sed --in-place "s/dbname=noa user=noa/dbname=masi user=masi"

(你也可以用 find 的 -exec 选项来实现这个功能,不过我个人更喜欢用 xargs。)

9

你真的需要正则表达式吗?

import os

def recursive_replace( root, pattern, replace )
    for dir, subdirs, names in os.walk( root ):
        for name in names:
            path = os.path.join( dir, name )
            text = open( path ).read()
            if pattern in text:
                open( path, 'w' ).write( text.replace( pattern, replace ) )
26

把所有这些代码放到一个叫做 mass_replace 的文件里。在Linux或Mac OS X系统下,你可以用 chmod +x mass_replace 命令来给这个文件添加执行权限,然后直接运行它。在Windows系统下,你可以用 python mass_replace 命令加上相应的参数来运行。

#!/usr/bin/python

import os
import re
import sys

# list of extensions to replace
DEFAULT_REPLACE_EXTENSIONS = None
# example: uncomment next line to only replace *.c, *.h, and/or *.txt
# DEFAULT_REPLACE_EXTENSIONS = (".c", ".h", ".txt")

def try_to_replace(fname, replace_extensions=DEFAULT_REPLACE_EXTENSIONS):
    if replace_extensions:
        return fname.lower().endswith(replace_extensions)
    return True


def file_replace(fname, pat, s_after):
    # first, see if the pattern is even in the file.
    with open(fname) as f:
        if not any(re.search(pat, line) for line in f):
            return # pattern does not occur in file so we are done.

    # pattern is in the file, so perform replace operation.
    with open(fname) as f:
        out_fname = fname + ".tmp"
        out = open(out_fname, "w")
        for line in f:
            out.write(re.sub(pat, s_after, line))
        out.close()
        os.rename(out_fname, fname)


def mass_replace(dir_name, s_before, s_after, replace_extensions=DEFAULT_REPLACE_EXTENSIONS):
    pat = re.compile(s_before)
    for dirpath, dirnames, filenames in os.walk(dir_name):
        for fname in filenames:
            if try_to_replace(fname, replace_extensions):
                fullname = os.path.join(dirpath, fname)
                file_replace(fullname, pat, s_after)

if len(sys.argv) != 4:
    u = "Usage: mass_replace <dir_name> <string_before> <string_after>\n"
    sys.stderr.write(u)
    sys.exit(1)

mass_replace(sys.argv[1], sys.argv[2], sys.argv[3])

更新:我对上面的代码做了一些修改。首先,mass_replace() 现在使用 re.compile() 来提前编译搜索模式;其次,为了检查文件的扩展名,我们现在把文件扩展名放在一个元组里传给 .endswith(),而不是调用三次 .endswith();第三,现在使用了Python新版本中的 with 语句;最后,file_replace() 现在会检查文件中是否找到了模式,如果没有找到,就不会重写文件。(旧版本会重写每个文件,即使输出文件和输入文件完全一样,时间戳也会改变,这样做不太优雅。)

更新:我把默认设置改成了替换每个文件,但你可以通过一行代码来限制只替换特定扩展名的文件。我觉得默认替换每个文件更实用。这个功能可以扩展,添加一个不处理的扩展名或文件名列表,或者增加不区分大小写的选项等等。

更新:在一个评论中,@asciimo 指出了一个bug。我修改了代码来修复这个bug。str.endswith() 的文档说明它可以接受一个字符串元组,但不能接受列表。这个问题已经修复。此外,我让几个函数接受一个可选参数,这样你可以传入一个扩展名的元组;修改成接受命令行参数来指定扩展名应该也很简单。

撰写回答