<p>在纯Python中使用<em>no</em>外部命令或附加依赖项创建一个自制的<code>sed</code>替换是一项崇高的任务,充满了崇高的地雷。谁会想到呢?</p>
<p><strong>尽管如此,这是可行的。</strong>这也是可取的。我们都去过那里,人们:“我需要咀嚼一些纯文本文件,但我只有Python,两个塑料鞋带,和一罐发霉的地堡级马拉希诺樱桃。帮助。”</p>
<p>在这个答案中,我们提供了一个最佳的解决方案,将先前答案的令人不快的地方拼凑在一起,而不是令人不快的地方。正如plundra所指出的,David Miller的<a href="https://stackoverflow.com/a/4427835/2809027">otherwise top-notch answer</a>以非原子的方式写入所需的文件,因此会引发竞争条件(例如,来自其他试图同时读取该文件的线程和/或进程)。真糟糕。Plundra的<a href="https://stackoverflow.com/a/4428373/2809027">otherwise excellent answer</a>解决了<em>这个</em>问题,同时引入了更多的错误,包括许多致命的编码错误、一个关键的安全漏洞(无法保留原始文件的权限和其他元数据)以及过早的优化,用低级字符索引替换正则表达式。那也不好。</p>
<p>太棒了,团结起来!</p>
<pre><code>import re, shutil, tempfile
def sed_inplace(filename, pattern, repl):
'''
Perform the pure-Python equivalent of in-place `sed` substitution: e.g.,
`sed -i -e 's/'${pattern}'/'${repl}' "${filename}"`.
'''
# For efficiency, precompile the passed regular expression.
pattern_compiled = re.compile(pattern)
# For portability, NamedTemporaryFile() defaults to mode "w+b" (i.e., binary
# writing with updating). This is usually a good thing. In this case,
# however, binary writing imposes non-trivial encoding constraints trivially
# resolved by switching to text writing. Let's do that.
with tempfile.NamedTemporaryFile(mode='w', delete=False) as tmp_file:
with open(filename) as src_file:
for line in src_file:
tmp_file.write(pattern_compiled.sub(repl, line))
# Overwrite the original file with the munged temporary file in a
# manner preserving file attributes (e.g., permissions).
shutil.copystat(filename, tmp_file.name)
shutil.move(tmp_file.name, filename)
# Do it for Johnny.
sed_inplace('/etc/apt/sources.list', r'^\# deb', 'deb')
</code></pre>