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


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.