質問
テキスト ファイルの内容をループして、いくつかの行を検索および置換し、結果をファイルに書き戻したいと考えています。最初にファイル全体をメモリにロードしてから書き戻すこともできますが、それはおそらく最良の方法ではありません。
次のコード内でこれを行う最善の方法は何ですか?
f = open(file)
for line in f:
if line.contains('foo'):
newline = line.replace('foo', 'bar')
# how to write this newline back to the file
解決
このようなことができるはずだと思います。基本的には、コンテンツを新しいファイルに書き込み、古いファイルを新しいファイルに置き換えます。
from tempfile import mkstemp
from shutil import move
from os import fdopen, remove
def replace(file_path, pattern, subst):
#Create temp file
fh, abs_path = mkstemp()
with fdopen(fh,'w') as new_file:
with open(file_path) as old_file:
for line in old_file:
new_file.write(line.replace(pattern, subst))
#Remove original file
remove(file_path)
#Move new file
move(abs_path, file_path)
他のヒント
おそらく最短の方法は、 ファイル入力モジュール. 。たとえば、次の例ではファイルに行番号をインプレースで追加します。
import fileinput
for line in fileinput.input("test.txt", inplace=True):
print "%d: %s" % (fileinput.filelineno(), line),
ここで何が起こるかというと、次のとおりです。
- 元のファイルはバックアップ ファイルに移動されます
- 標準出力はループ内の元のファイルにリダイレクトされます。
- したがって、任意の
print
ステートメントは元のファイルに書き戻します
fileinput
さらに追加機能があります。たとえば、次のすべてのファイルを自動的に操作するために使用できます。 sys.args[1:]
, 明示的に反復処理する必要はありません。Python 3.2 以降では、 with
声明。
その間 fileinput
これは使い捨てのスクリプトには最適ですが、確かにあまり読みにくく、見慣れないため、実際のコードで使用することには慎重です。実際の (運用) コードでは、プロセスを明示的にしてコードを読みやすくするために、さらに数行のコードを費やす価値があります。
次の 2 つのオプションがあります。
- ファイルはそれほど大きくないので、完全にメモリに読み込むことができます。次に、ファイルを閉じ、書き込みモードで再度開き、変更した内容を書き込みます。
- ファイルが大きすぎるためメモリに保存できません。これを一時ファイルに移動して開き、一行ずつ読み取って元のファイルに書き戻すことができます。これには 2 倍のストレージが必要になることに注意してください。
以下はテスト済みの別の例で、検索と置換のパターンに一致します。
import fileinput
import sys
def replaceAll(file,searchExp,replaceExp):
for line in fileinput.input(file, inplace=1):
if searchExp in line:
line = line.replace(searchExp,replaceExp)
sys.stdout.write(line)
使用例:
replaceAll("/fooBar.txt","Hello\sWorld!$","Goodbye\sWorld.")
これは機能するはずです:(インプレース編集)
import fileinput
# Does a list of files, and
# redirects STDOUT to the file in question
for line in fileinput.input(files, inplace = 1):
print line.replace("foo", "bar"),
Thomas Watnedal による回答に基づいています。ただし、これは元の質問の行ごとの部分に正確に答えているわけではありません。この関数は引き続き行ごとに置き換えることができます。
この実装では、一時ファイルを使用せずにファイルの内容が置き換えられるため、ファイルのアクセス許可は変更されません。
また、replace の代わりに re.sub を使用すると、プレーン テキストのみの置換ではなく正規表現の置換が可能になります。
ファイルを 1 行ずつではなく単一の文字列として読み取ることで、複数行の一致と置換が可能になります。
import re
def replace(file, pattern, subst):
# Read contents from file as a single string
file_handle = open(file, 'r')
file_string = file_handle.read()
file_handle.close()
# Use RE package to allow for replacement (also allowing for (multiline) REGEX)
file_string = (re.sub(pattern, subst, file_string))
# Write contents to file.
# Using mode 'w' truncates the file.
file_handle = open(file, 'w')
file_handle.write(file_string)
file_handle.close()
lassevk が示唆しているように、新しいファイルを書き出すと、コード例がいくつか示されます。
fin = open("a.txt")
fout = open("b.txt", "wt")
for line in fin:
fout.write( line.replace('foo', 'bar') )
fin.close()
fout.close()
を置き換える汎用関数が必要な場合 どれでも テキストと他のテキストを組み合わせる場合、特に正規表現のファンの場合、これがおそらく最良の方法です。
import re
def replace( filePath, text, subs, flags=0 ):
with open( filePath, "r+" ) as file:
fileContents = file.read()
textPattern = re.compile( re.escape( text ), flags )
fileContents = textPattern.sub( subs, fileContents )
file.seek( 0 )
file.truncate()
file.write( fileContents )
より Python 的な方法は、以下のコードのようなコンテキスト マネージャーを使用することです。
from tempfile import mkstemp
from shutil import move
from os import remove
def replace(source_file_path, pattern, substring):
fh, target_file_path = mkstemp()
with open(target_file_path, 'w') as target_file:
with open(source_file_path, 'r') as source_file:
for line in source_file:
target_file.write(line.replace(pattern, substring))
remove(source_file_path)
move(target_file_path, source_file_path)
完全なスニペットを見つけることができます ここ.
新しいファイルを作成し、古いファイルから新しいファイルに行をコピーし、新しいファイルに行を書き込む前に置換を実行します。
@Kiranの答えを拡張すると、これはより簡潔でPython的であることに同意しますが、UTF-8の読み取りと書き込みをサポートするコーデックを追加します。
import codecs
from tempfile import mkstemp
from shutil import move
from os import remove
def replace(source_file_path, pattern, substring):
fh, target_file_path = mkstemp()
with codecs.open(target_file_path, 'w', 'utf-8') as target_file:
with codecs.open(source_file_path, 'r', 'utf-8') as source_file:
for line in source_file:
target_file.write(line.replace(pattern, substring))
remove(source_file_path)
move(target_file_path, source_file_path)
hamishmcn の回答をテンプレートとして使用すると、正規表現に一致するファイル内の行を検索し、それを空の文字列に置き換えることができました。
import re
fin = open("in.txt", 'r') # in file
fout = open("out.txt", 'w') # out file
for line in fin:
p = re.compile('[-][0-9]*[.][0-9]*[,]|[-][0-9]*[,]') # pattern
newline = p.sub('',line) # replace matching strings with empty string
print newline
fout.write(newline)
fin.close()
fout.close()
以下のようにインデントを外すと複数行で検索・置換されます。たとえば、以下を参照してください。
def replace(file, pattern, subst):
#Create temp file
fh, abs_path = mkstemp()
print fh, abs_path
new_file = open(abs_path,'w')
old_file = open(file)
for line in old_file:
new_file.write(line.replace(pattern, subst))
#close temp file
new_file.close()
close(fh)
old_file.close()
#Remove original file
remove(file)
#Move new file
move(abs_path, file)
Linux ユーザーの場合:
import os
os.system('sed -i \'s/foo/bar/\' '+file_path)