Pythonでファイル内の行を検索して置換する


293

テキストファイルの内容をループして、いくつかの行で検索と置換を行い、結果をファイルに書き戻したいのですが。最初にファイル全体をメモリにロードしてから書き戻すことができますが、それがおそらく最善の方法ではありません。

次のコード内でこれを行うための最良の方法は何ですか?

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

回答:


191

私はこのような何かがそれをするべきだと思います。基本的にはコンテンツを新しいファイルに書き込み、古いファイルを新しいファイルに置き換えます。

from tempfile import mkstemp
from shutil import move, copymode
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))
    #Copy the file permissions from the old file to the new file
    copymode(file_path, abs_path)
    #Remove original file
    remove(file_path)
    #Move new file
    move(abs_path, file_path)

5
ほんの小さなコメント:file同じ名前の事前定義されたクラスを隠しています。
ezdazuzena 2013年

4
このコードは、元のファイルの権限を変更します。元の権限を維持するにはどうすればよいですか?
nic 2013

1
fhのポイントは何ですか、それをclose呼び出しで使用しますが、ファイルを作成してファイルを閉じるだけの意味はわかりません...
Wicelo

2
@Wiceloファイル記述子のリークを防ぐために、ファイルを閉じる必要があります。これはまともな説明です:logilab.org/17873
Thomas Watnedal '19

1
はい、それがmkstemp()2タプルを返していることを発見しました(fh, abs_path) = fh, abs_path。質問したとき、そのことを知りませんでした。
Wicelo

272

最も短い方法は、おそらくfileinputモジュールを使用することでしょう。たとえば、次の例では、行番号をファイルにインプレースで追加しています。

import fileinput

for line in fileinput.input("test.txt", inplace=True):
    print('{} {}'.format(fileinput.filelineno(), line), end='') # for Python 3
    # print "%d: %s" % (fileinput.filelineno(), line), # for Python 2

ここで何が起こるか:

  1. 元のファイルはバックアップファイルに移動されます
  2. 標準出力はループ内で元のファイルにリダイレクトされます
  3. したがって、すべてのprintステートメントは元のファイルに書き戻します

fileinputより多くの鐘と笛を持っています。たとえば、sys.args[1:]明示的に反復することなく、のすべてのファイルを自動的に操作するために使用できます。Python 3.2からは、withステートメントで使用するための便利なコンテキストマネージャも提供しています。


ながら fileinput使い捨てスクリプトのための素晴らしいです確かに、それは非常に読みやすいか、慣れていないですので、私は実際のコードでそれを使用しての警戒するでしょう。実際の(本番)コードでは、プロセスを明示的にしてコードを読み取り可能にするために、数行のコードを費やすだけの価値があります。

2つのオプションがあります。

  1. ファイルは大きすぎず、完全にメモリに読み込むことができます。次にファイルを閉じ、書き込みモードで再度開き、変更した内容を書き戻します。
  2. ファイルが大きすぎてメモリに保存できません。一時ファイルに移動してそれを開き、1行ずつ読み取り、元のファイルに書き戻すことができます。これには2倍のストレージが必要になることに注意してください。

13
これには2行しかないことを知っていますが、コード自体は非常に表現力があるとは思いません。少し考えてみても、その機能を知らなかったとしたら、何が起こっているのかについての手がかりはほとんどありません。行番号と行を印刷することは、それを書くことと同じではありません...私の要点が
わかったら

14
これは、DOES、ファイルへの書き込みを。stdoutをファイルにリダイレクトします。見ていドキュメントを
ブライス

32
ここで重要なのは、printステートメントの最後にあるコンマです。これは、printステートメントが別の改行を追加するのを抑制します(行にはすでに1行あるため)。ただし、それはまったく明白ではありません(幸いなことに、Python 3がその構文を変更したのはそのためです)。
VPeric 2011年

4
ファイルに開始フックを提供する場合、たとえばUTF-16でエンコードされたファイルを読み書きしようとする場合、これは機能しないことに注意してください。
bompf 2013

5
python3の場合print(line, end='')
Ch.Idea

80

以下はテスト済みの別の例で、検索と置換のパターンに一致します。

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.")

23
使用例では正規表現が提供されていますが、正規表現の操作でsearchExp in lineline.replaceありません。確かに使用例は間違っています。
kojiro

if searchExp in line: line = line.replace(searchExp, replaceExpr)あなたの代わりにただ書くことができますline = line.replace(searchExp, replaceExpr)。例外は生成されず、ラインは変更されません。
デビッドウォレス

私にとっても完璧に働いた。これに非常に似ている他の例をいくつか見つけましたが、トリックはの使用でしたsys.stdout.write(line)。再度、感謝します!
セージ

これを使用すると、ファイルが空白になります。何か案が?
JavierLópezTomás19年

私はこれを使用しています
Rakib Fiha

64

これは動作するはずです:(インプレース編集)

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"),

5
+1。また、RuntimeError:input()がすでにアクティブになっている場合は、fileinput.close()を呼び出します
geographika

1
ファイルオブジェクトfilesはなく、ファイル名を含む文字列である必要があることに注意してください。
atomh33ls 2013

9
printは、すでにそこにあるかもしれない改行を追加します。これを回避するには、置換の最後に.rstrip()を追加します
Guillaume Gendre

代わりに、input()でfiles argを使用します。fileinput.input(inplace = 1)とし、> python replace.py myfiles * .txt
chespinoza

24

Thomas Watnedalの回答に基づく。ただし、これは元の質問の行ごとの部分に正確に答えるものではありません。関数は引き続き行ごとに置き換えることができます

この実装では、一時ファイルを使用せずにファイルの内容を置き換えます。その結果、ファイルのアクセス許可は変更されません。

また、置換の代わりにre.subを使用すると、プレーンテキストの代わりに正規表現の置換のみが可能になります。

ファイルを1行ずつではなく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()

2
ファイルを開くときにrbwb属性を使用すると、元の行末が維持されます
Nux

Python 3では、「wb」と「rb」を「re」と一緒に使用することはできません。「TypeError:バイトのようなオブジェクトでは文字列パターンを使用できません」というエラーが発生します

15

lassevkが示唆するように、移動しながら新しいファイルを書き出します。ここにいくつかのサンプルコードがあります。

fin = open("a.txt")
fout = open("b.txt", "wt")
for line in fin:
    fout.write( line.replace('foo', 'bar') )
fin.close()
fout.close()

12

任意のテキストを他のテキストに置き換える汎用関数が必要な場合、これはおそらく正規表現のファンである場合に最適な方法です。

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 )

12

よりパイソン的な方法は、以下のコードのようなコンテキストマネージャを使用することです:

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)

完全なスニペットはここにあります


Python> = 3.1では、2つのコンテキストマネージャを同じ行で開くことができます。
florisla

4

新しいファイルを作成し、古いファイルから新しいファイルに行をコピーし、新しいファイルに行を書き込む前に置き換えを行います。


4

@Kiranの回答を拡張すると、私は同意しますが、これはより簡潔でPythonicであり、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)

古いファイルの権限を新しいファイルに保存しますか?
Bidyut 2017

2

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()

1
forループの外側で正規表現をコンパイルする必要があります。コンパイルしないと、パフォーマンスが低下します
Axel

2

fileinput 以前の回答で述べたように非常に簡単です:

import fileinput

def replace_in_file(file_path, search_text, new_text):
    with fileinput.input(file_path, inplace=True) as f:
        for line in f:
            new_line = line.replace(search_text, new_text)
            print(new_line, end='')

説明:

  • fileinput複数のファイルを受け入れることができますが、処理中の各ファイルはすぐに閉じることをお勧めします。したがってfile_pathwithステートメントに1つ配置されます。
  • printは元のファイルに転送されるinplace=Trueため、ステートメントは何も出力しませんSTDOUT
  • end=''print声明中間空白新しい行を排除することです。

次のように使用できます。

file_path = '/path/to/my/file'
replace_in_file(file_path, 'old-text', 'new-text')

0

以下のようにインデントを削除すると、複数行で検索して置換されます。例については、以下を参照してください。

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)

このPythonコードのフォーマットが正しくありません...(修正しようとしましたが、何が意図されているのか
Andy Hayden
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.