파이썬에서 파일에서 줄 검색 및 바꾸기


292

텍스트 파일의 내용을 반복하고 검색을 수행하고 일부 줄을 바꾸고 결과를 파일에 다시 씁니다. 먼저 전체 파일을 메모리에로드 한 다음 다시 쓸 수는 있지만 아마도 최선의 방법은 아닙니다.

다음 코드에서이를 수행하는 가장 좋은 방법은 무엇입니까?

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

4
이 코드는 원본 파일에 대한 권한을 변경합니다. 원래 권한을 유지하려면 어떻게해야합니까?
— nic

1
fh의 요점은 무엇입니까, 당신은 근접 전화에서 그것을 사용하지만 파일을 닫는 파일을 만드는 요점을 볼 수 없습니다 ...
— Wicelo

2
@Wicelo 파일 디스크립터의 누출을 방지하기 위해 파일을 닫아야합니다. 알맞은 설명은 다음과 같습니다. logilab.org/17873
— Thomas Watnedal

1
예 , 나는 그것이 mkstemp()2 튜플을 반환하고 있음을 발견 (fh, abs_path) = fh, abs_path했으며 질문을했을 때 그것을 알지 못했습니다.
— Wicelo

271

가장 짧은 방법은 아마도 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일회용 스크립트를 위해 중대하다시피 그렇지 않은 매우 읽기 쉽고 잘 알고 있기 때문에, 나는 실제 코드에서 사용주의 될 것이다. 실제 (프로덕션) 코드에서는 프로세스를 명시 적으로 작성하여 코드를 읽을 수 있도록 몇 줄의 코드 만 더 사용하는 것이 좋습니다.

두 가지 옵션이 있습니다.

  1. 파일이 너무 크지 않으므로 메모리 전체를 읽을 수 있습니다. 그런 다음 파일을 닫고 쓰기 모드로 다시 연 다음 수정 된 내용을 다시 쓰십시오.
  2. 파일이 너무 커서 메모리에 저장되지 않습니다. 파일을 임시 파일로 옮길 수 있으며 파일을 한 줄씩 읽고 원본 파일에 다시 쓸 수 있습니다. 이를 위해서는 두 배의 스토리지가 필요합니다.

13
나는 이것에 두 줄만 있다는 것을 알고 있지만 코드 자체가 매우 표현력이 없다고 생각합니다. 잠깐 생각하면 기능을 몰랐다면 무슨 일이 일어나고 있는지에 대한 단서가 거의 없습니다. 줄 번호와 줄을 인쇄하는 것은 그것을 쓰는 것과 같지 않습니다 ... 당신이 내 요지를 얻는다면 ...
— chutsu

14
이것은 파일 에 씁니다. stdout을 파일로 경로 재 지정합니다. 문서를
— brice

32
여기서 키 비트는 print 문 끝의 쉼표입니다. 줄이 이미 있으면 다른 줄 바꿈을 추가하여 print 문을 숨 깁니다. 그럼에도 불구하고 전혀 명확하지 않습니다 (파이썬 3이 운 좋게도 그 구문을 바꿨습니다).
— VPeric

4
파일에 오프닝 후크를 제공 할 때 (예 : UTF-16 인코딩 파일을 읽거나 쓰려고 할 때) 이것은 작동하지 않습니다.
— bompf

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 line아닙니다 line.replace. 분명히 예제 사용이 잘못되었습니다.
— kojiro

대신 if searchExp in line: line = line.replace(searchExp, replaceExpr)당신은 쓸 수 있습니다 line = line.replace(searchExp, replaceExpr). 예외는 생성되지 않으며 라인은 변경되지 않습니다.
— David Wallace

나를 위해 완벽하게 일했습니다. 나는 이것과 매우 유사한 다른 많은 예제를 보았지만 요령은의 사용이었습니다 sys.stdout.write(line). 다시 감사합니다!
— Sage

이것을 사용하면 파일이 비게됩니다. 어떤 생각?
— Javier López Tomás

나는 이것을 사용하고있다
— 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

9
print는 이미있을 수있는 개행을 추가합니다. 이 문제를 피하려면 교체가 끝날 때 .rstrip ()을 추가하십시오
— Guillaume Gendre

대신 파일을 입력 ()에 arg를 사용, 그것은 (인플레 이스 = 1) fileinput.input하고 같은 스크립트를 호출 할 수 있습니다> 파이썬 replace.py myfiles * .txt 인
— chespinoza

24

Thomas Watnedal의 답변을 기반으로합니다. 그러나 이것은 원래 질문의 행간 부분에 정확하게 대답하지는 않습니다. 이 기능은 여전히 ​​라인 단위로 교체 할 수 있습니다

이 구현은 임시 파일을 사용하지 않고 파일 내용을 대체하므로 파일 권한이 변경되지 않습니다.

또한 replace 대신 re.sub를 사용하면 일반 텍스트 대체 대신 정규식 대체를 허용합니다.

파일을 줄 단위 대신 단일 문자열로 읽으면 여러 줄 일치 및 교체가 가능합니다.

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
파일을 열 때 원래 줄 끝을 유지 하기 때문에 사용 rb하고 wb속성 을 사용할 수 있습니다.
— Nux

파이썬 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에서는 두 줄의 컨텍스트 관리자를 같은 줄에서 열 수 있습니다 .
— 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

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_path에서 단일 배치 with.
  • 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)

꽤 제대로 보이지 않습니다이 파이썬 코드의 포맷 (내가 수정하려고했으나 의도하지 않은 있는지 무엇을)
— 앤디 헤이든
당사 사이트를 사용함과 동시에 당사의 쿠키 정책과 개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.