पायथन में एक फ़ाइल में एक लाइन खोजें और बदलें


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

जवाबों:


193

मुझे लगता है कि कुछ ऐसा करना चाहिए। यह मूल रूप से एक नई फ़ाइल के लिए सामग्री लिखता है और पुरानी फ़ाइल को नई फ़ाइल से बदल देता है:

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
यह कोड मूल फ़ाइल पर अनुमतियों को बदलता है। मैं मूल अनुमतियां कैसे रख सकता हूं?
निक

1
fh का क्या मतलब है, आप इसे बंद कॉल में उपयोग करते हैं, लेकिन मैं इसे बंद करने के लिए केवल एक फ़ाइल बनाने की बात नहीं देखता ...
विसेलो

2
@Wicelo फ़ाइल डिस्क्रिप्टर की लीक को रोकने के लिए आपको इसे बंद करने की आवश्यकता है। यहाँ एक अच्छी व्याख्या है: logilab.org/17873
थॉमस वात्सल

1
हां, मुझे पता चला है कि mkstemp()2-ट्यूपल लौट (fh, abs_path) = fh, abs_pathरहा है और , मुझे नहीं पता था कि जब मैंने सवाल पूछा था।
विसेलो

272

संभवतया सबसे छोटा तरीका फाइलइनपुट मॉड्यूल का उपयोग करना होगा । उदाहरण के लिए, निम्नलिखित फ़ाइल में लाइन नंबर को जगह में जोड़ता है:

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:], बिना आपके द्वारा उन पर स्पष्ट रूप से पुनरावृत्ति करने के लिए। पायथन 3.2 के साथ शुरू यह एक withबयान में उपयोग के लिए एक सुविधाजनक संदर्भ प्रबंधक प्रदान करता है ।


जबकि fileinputthrowaway लिपियों के लिए अच्छा है, मैं वास्तविक कोड में इसका उपयोग करने से सावधान रहना होगा क्योंकि बेशक यह नहीं बहुत पठनीय या परिचित है। वास्तविक (उत्पादन) कोड में प्रक्रिया को स्पष्ट करने के लिए कोड की कुछ और पंक्तियों को खर्च करना सार्थक है और इस प्रकार कोड को पठनीय बनाया जा सकता है।

दो विकल्प हैं:

  1. फ़ाइल बहुत बड़ी नहीं है, और आप इसे पूरी तरह से मेमोरी में पढ़ सकते हैं। फिर फ़ाइल बंद करें, इसे लेखन मोड में फिर से खोलें और संशोधित सामग्री वापस लिखें।
  2. फ़ाइल स्मृति में संग्रहीत होने के लिए बहुत बड़ी है; आप इसे एक अस्थायी फ़ाइल पर ले जा सकते हैं और इसे खोल सकते हैं, इसे लाइन से पढ़कर, मूल फ़ाइल में वापस लिख सकते हैं। ध्यान दें कि इसके लिए दो बार संग्रहण की आवश्यकता है।

13
मुझे पता है कि इसमें केवल दो लाइनें हैं, हालांकि मुझे नहीं लगता कि यह कोड अपने आप में बहुत स्पष्ट है। क्योंकि यदि आप एक सेकंड के लिए सोचते हैं, यदि आप फ़ंक्शन को नहीं जानते हैं, तो जो कुछ चल रहा है उसमें बहुत कम सुराग हैं। लाइन नंबर और लाइन को प्रिंट करना, इसे लिखने के समान नहीं है ... यदि आपको मेरा
जिस्म

14
यह करता है फ़ाइल के लिए लिखने। यह फ़ाइल पर stdout रीडायरेक्ट करता है। डॉक्स
brice

32
यहाँ कुंजी बिट प्रिंट स्टेटमेंट के अंत में अल्पविराम है: यह प्रिंट स्टेटमेंट को एक और न्यूलाइन (जैसे कि लाइन में पहले से एक है) जोड़ देता है। यह बिल्कुल स्पष्ट नहीं है, हालांकि (यही कारण है कि पायथन 3 ने उस वाक्य रचना को बदल दिया, सौभाग्य से पर्याप्त है)।
VPeric

4
कृपया ध्यान दें कि जब आप UTF-16 एन्कोडेड फ़ाइलों को पढ़ने / लिखने का प्रयास करते हैं, तो यह फ़ाइल के लिए एक शुरुआती हुक प्रदान करने पर काम नहीं करता है।
बोमफ

5
पायथन 3 के लिए,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नियमित अभिव्यक्ति के संचालन कर रहे हैं। निश्चित रूप से उदाहरण का उपयोग गलत है।
कोजीरो

if searchExp in line: line = line.replace(searchExp, replaceExpr)आप के बजाय बस लिख सकते हैं line = line.replace(searchExp, replaceExpr)। कोई अपवाद उत्पन्न नहीं होता है, लाइन केवल अपरिवर्तित रहती है।
डेविड वालेस

मेरे लिए भी पूरी तरह से काम किया। मैं कई अन्य उदाहरणों के साथ आया था जो इस के समान थे, लेकिन चाल का उपयोग किया गया था sys.stdout.write(line)। एक बार फिर धन्यवाद!
ऋषि

यदि मैं इसका उपयोग करता हूं, तो मेरी फ़ाइल रिक्त हो जाती है। कोई उपाय?
जेवियर लोपेज़ टामसे

मैं इसका उपयोग कर रहा हूँ
रकीब फ़िहा

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: इनपुट () पहले से सक्रिय है तो fileinput.close () को कॉल करें
geographika

1
ध्यान दें कि filesफ़ाइल नाम वाला स्ट्रिंग होना चाहिए, न कि फ़ाइल ऑब्जेक्ट
atomh33ls 10

9
प्रिंट एक नई पंक्ति जोड़ता है जो पहले से ही हो सकता है। इससे बचने के लिए, अपने प्रतिस्थापन के अंत में .rstrip () जोड़ें
Guillaume Gendre

इनपुट () में arg फाइल का उपयोग करने के बजाय, यह fileinput.input (inplace = 1) हो सकता है और स्क्रिप्ट को python प्रतिस्थापनहोम myfiles * .txt
chespinoza के

24

थॉमस वात्डल के जवाब के आधार पर। हालाँकि, यह मूल प्रश्न के लाइन-टू-लाइन भाग का उत्तर नहीं देता है। फ़ंक्शन अभी भी एक लाइन-टू-लाइन आधार पर बदल सकता है

यह कार्यान्वयन अस्थायी फ़ाइलों का उपयोग किए बिना फ़ाइल सामग्री की जगह लेता है, परिणामस्वरूप फ़ाइल अनुमतियाँ अपरिवर्तित रहती हैं।

रिप्लेस के बजाय रीबस करें, केवल प्लेन टेक्स्ट रिप्लेसमेंट के बजाय रेगेक्स रिप्लेसमेंट की अनुमति देता है।

लाइन द्वारा लाइन के बजाय फाइल को एक स्ट्रिंग के रूप में पढ़ना मल्टीलाइन मैच और प्रतिस्थापन के लिए अनुमति देता है।

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विशेषताएँ करना चाह सकते हैं क्योंकि यह मूल लाइन एंडिंग को संरक्षित करेगा
नक्स

पायथन 3 में, आप 'पुनः' के साथ 'wb' और 'rb' का उपयोग नहीं कर सकते। यह त्रुटि देगा "TypeError: बाइट्स जैसी ऑब्जेक्ट पर एक स्ट्रिंग पैटर्न का उपयोग नहीं कर सकता"

15

जैसा कि लेज़्सेव सुझाव देता है, नई फ़ाइल को बाहर जाने के रूप में लिखें, यहाँ कुछ उदाहरण कोड है:

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)

आप यहां पूरा स्निपेट देख सकते हैं ।


पायथन में = = 3.1 आप एक ही पंक्ति में दो संदर्भ प्रबंधक खोल सकते हैं ।
फ्लोरिसला

4

एक नई फ़ाइल बनाएँ, पुरानी से नई लाइनों को कॉपी करें, और नई फ़ाइल को लाइनें लिखने से पहले आप इसे प्रतिस्थापित करें।


4

@ किरण के उत्तर पर विस्तार करते हुए, जो मैं सहमत हूं कि अधिक रसीला और पायथोनिक है, यह 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)

क्या यह नई फ़ाइल में पुरानी फ़ाइल की अनुमति को संरक्षित करने वाला है?
बिद्दुत

2

एक टेम्पलेट के रूप में हैमिशकेन के उत्तर का उपयोग करना मैं एक फ़ाइल में एक पंक्ति की खोज करने में सक्षम था जो मेरे रेगेक्स से मेल खाती है और इसे खाली स्ट्रिंग के साथ बदल रही है।

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
आपको लूप के लिए रेगेक्स OUTSIDE को संकलित करना चाहिए, अन्यथा एक प्रदर्शन बेकार है
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)

इस पायथन कोड का प्रारूपण बहुत सही नहीं लगता है ... (मैंने ठीक करने की कोशिश की, लेकिन यह निश्चित नहीं था कि क्या इरादा था)
एंडी हेडन
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.