用Python在给定目录及其子目录中递归替换文件中的字符串?
Posted
技术标签:
【中文标题】用Python在给定目录及其子目录中递归替换文件中的字符串?【英文标题】:Replace strings in files by Python, recursively in given directory and its subdirectories? 【发布时间】:2010-12-08 12:46:58 【问题描述】:如何在给定目录及其子目录中递归地用给定替换替换文件中的字符串匹配?
伪代码:
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
【问题讨论】:
钉子锤,螺丝刀螺丝。如果你真的需要特别强调效率,Perl 是完成这项工作的完美工具。不是说“不要使用 Python”,而是考虑你的要求。 Chris:我知道 Perl,但我也想知道 Python。 @Chris - Perl?find
和 xargs
怎么样?
【参考方案1】:
你真的需要正则表达式吗?
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 ) )
【讨论】:
在这种情况下,Globbing 似乎比正则表达式更好。 这里的“root”真的是指目录层次结构的最低级别吗?或者它只是一个变量,您可以在其中存储要搜索的路径? @Masi: "root" 只是一个参数名,开始搜索的最底层目录。你可以给它起任何你喜欢的名字【参考方案2】:将所有这些代码放入一个名为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 在评论中指出了一个错误。我对此进行了编辑以修复错误。 str.endswith()
被记录为接受要尝试的字符串元组,但不接受列表。固定的。另外,我让几个函数接受一个可选参数,让你传入一个扩展元组;修改它以接受命令行参数来指定哪些扩展应该很容易。
【讨论】:
我喜欢你的代码将逻辑部分分离为函数的逻辑。 在file_replace
中,我必须将 os.rename
更改为 shutil.move
才能在 Windows 中为我工作。
在我的系统(python 2.7.5)上,我得到了TypeError: endswith first arg must be str, unicode, or tuple, not list
。将列表更改为元组有效,例如[".fudge", "pancake"]
-> (".fudge", "pancake")
.
@asciimo,感谢您指出这一点。通常我很擅长在将代码发布到这里之前对其进行测试,但我想我写的时候很草率!现已修复。
Windows 的两个修复:1) 取消缩进 os.rename(out_fname, fname)
,使其位于 with
范围之外。 2) 在这行之前加上os.remove(fname)
以便rename()
成功。【参考方案3】:
当然,如果你只是想完成它而不编码它,使用 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。)
【讨论】:
find 和 sed 解决方案适用于简单任务,例如“替换每个 *.txt 文件中的字符串”。一旦你有一组更复杂的文件要匹配,如果你有多个替换要做,Python 解决方案真的会赢。【参考方案4】:这就是我如何使用 python 查找和替换文件中的字符串。这是一个简单的小函数,它将递归地在目录中搜索字符串并将其替换为字符串。您还可以限制具有特定文件扩展名的文件,如下例所示。
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")
【讨论】:
【参考方案5】:这应该可行:
import re, os
import fnmatch
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("namelist.wps", 'a') as out:
with open("namelist.wps", 'r') as readf:
for line in readf:
line = re.sub(r"dbname=noa user=noa", "dbname=masi user=masi", line)
out.write(line)
【讨论】:
我忘记了:import fnmatch 欢迎来到 ***!您可以通过单击问题正文底部的edit
来编辑您的答案。请看看编辑是否正确。我喜欢你的with open("namelist.wps", 'r') as readf
,这比把事情分成两行要清楚得多。最后一个 for 循环也很清楚。非常好的补充!以上是关于用Python在给定目录及其子目录中递归替换文件中的字符串?的主要内容,如果未能解决你的问题,请参考以下文章
如何在 PHP 中递归删除目录及其全部内容(文件 + 子目录)? [复制]