Создание собственной замены sed
на чистом Python с помощью нет внешних команд или дополнительных зависимостей - благородная задача, обремененная благородными наземными минами. Кто бы мог подумать?
Тем не менее, это возможно. Это также желательно. Мы все были там, люди: «Мне нужно взломать несколько текстовых файлов, но у меня есть только Python, два пластиковых шнурка и заплесневелая банка с вишней Maraschino бункерного качества. Помощь».
В этом ответе мы предлагаем лучшее в своем роде решение, объединяющее удивительность предыдущих ответов без всей этой неприятной , а не -видности. Как отмечает Плендра, Дэвид Миллер , в противном случае первоклассный ответ , записывает желаемый файл неатомарным образом и, следовательно, предлагает условия гонки (например, из других потоков и / или процессов, пытающихся одновременно прочитать этот файл). Это плохо. отличный ответ Plundra решает эту проблему , в то же время вводя еще больше - включая многочисленные фатальные ошибки кодирования, критическую уязвимость безопасности (неспособность сохранить разрешения и другие метаданные исходного файла), и преждевременная оптимизация, заменяющая регулярные выражения низкоуровневой индексацией символов. Это тоже плохо.
Потрясающе, объединяйтесь!
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')