2010-12-09 13 views
44

का उपयोग कर एक टेक्स्ट फ़ाइल लाइन-वार पढ़ें, संपादित करें और लिखें, क्या रूबी में फ़ाइलों को पढ़ने, संपादित करने और लिखने का कोई अच्छा तरीका है?रुबी

मेरी ऑनलाइन खोज में मुझे यह सब कुछ एक सरणी में पढ़ने के लिए सुझाव दिया गया है, कहा गया सरणी संशोधित करें, फिर सब कुछ लिखें। मुझे लगता है कि एक बेहतर समाधान होना चाहिए, खासकर अगर मैं एक बहुत बड़ी फाइल से निपट रहा हूं।

कुछ की तरह:

myfile = File.open("path/to/file.txt", "r+") 

myfile.each do |line| 
    myfile.replace_puts('blah') if line =~ /myregex/ 
end 

myfile.close 

कहाँ replace_puts वर्तमान पंक्ति के बजाय (अधिक) अगली पंक्ति लिख के रूप में यह वर्तमान में विभाजक के बाद क्योंकि सूचक लाइन के अंत में है करता है (से अधिक लिखते थे)।

तो /myregex/ से मेल खाने वाली प्रत्येक पंक्ति को 'ब्ला' के साथ बदल दिया जाएगा। जाहिर है कि मेरे मन में जो कुछ है, उससे थोड़ा अधिक शामिल है, जहां तक ​​प्रसंस्करण है, और एक पंक्ति में किया जाएगा, लेकिन विचार वही है - मैं लाइन द्वारा एक फ़ाइल लाइन पढ़ना चाहता हूं, और कुछ पंक्तियों को संपादित करना चाहता हूं, और जब मैं कर रहा हूँ लिखो।

शायद कहने का एक तरीका है कि "अंतिम विभाजक के ठीक बाद वापस लौटें"? या each_with_index का उपयोग करने और लाइन इंडेक्स नंबर के माध्यम से लिखने का कोई तरीका? हालांकि, मुझे इस तरह के कुछ भी नहीं मिला।

मेरे पास अब तक का सबसे अच्छा समाधान चीजों को लाइन-वार पढ़ने के लिए है, उन्हें एक नई (अस्थायी) फ़ाइल लाइन-वार (संभवतः संपादित) में लिखें, फिर पुरानी फ़ाइल को नई temp फ़ाइल के साथ ओवरराइट करें और हटाएं। दोबारा, मुझे लगता है कि एक बेहतर तरीका होना चाहिए - मुझे नहीं लगता कि मुझे मौजूदा 1 जीबी फ़ाइल में कुछ लाइनों को संपादित करने के लिए सिर्फ एक नई 1gig फ़ाइल बनाना होगा।

+0

परिणामों पर विचार करें यदि आपके कोड को पढ़ने के बाद ओवरराइट किया गया था, प्रक्रिया के माध्यम से भागने में असफल रहे: आप फ़ाइल को नष्ट करने का जोखिम चलाते हैं। –

+0

ठीक है, एक फॉलो-अप प्रश्न के रूप में: कमांड लाइन से, आप यह कर सकते हैं: ruby ​​-pe "gsub (/ blah /, 'newstuff')" whatev.txt। यही वह है जो मैं करना चाहता हूं, लेकिन मैं इसे कमांड लाइन पर नहीं करना चाहता, मैं इसे कुछ बड़े अंदर रखना चाहता हूं। क्या कोई मुझे बता सकता है, आंतरिक रूप से, वह आदेश क्या कर रहा है जो फ़ाइल को संपादित करने का भ्रम देता है, लाइन से लाइन? क्या यह एक अस्थायी फ़ाइल, या सरणी का उपयोग कर रहा है? क्योंकि यह अब तक बहुत बड़ी फ़ाइलों पर काम करता प्रतीत होता है, यहां तक ​​कि यहां दिए गए सुझावों की तुलना में मोरेसो। – Hsiu

+0

यह एक अच्छा सवाल है। क्या आप इसे एक नए प्रश्न में बना सकते हैं? इससे दूसरों को इसे देखने और इसका उत्तर देने में बहुत आसान बनाता है।इसके अलावा, अगर इस प्रश्न का आपकी संतुष्टि का उत्तर दिया गया था, तो क्या आप उस उत्तर को स्वीकार कर सकते हैं? धन्यवाद! –

उत्तर

6

यदि आप लाइन द्वारा फ़ाइल लाइन को ओवरराइट करना चाहते हैं, तो आपको यह सुनिश्चित करना होगा कि नई लाइन की मूल रेखा के समान लंबाई हो। यदि नई लाइन लंबी है, तो इसका हिस्सा अगली पंक्ति में लिखा जाएगा। यदि नई रेखा कम है, तो पुरानी रेखा का शेष बस रहता है जहां यह रहता है। tempfile समाधान वास्तव में अधिक सुरक्षित है। लेकिन अगर आप एक जोखिम लेने के लिए तैयार हैं:

File.open('test.txt', 'r+') do |f| 
    old_pos = 0 
    f.each do |line| 
     f.pos = old_pos # this is the 'rewind' 
     f.print line.gsub('2010', '2011') 
     old_pos = f.pos 
    end 
end 

लाइन आकार में बदल जाती है, तो यह एक संभावना है:

File.open('test.txt', 'r+') do |f| 
    out = "" 
    f.each do |line| 
     out << line.gsub(/myregex/, 'blah') 
    end 
    f.pos = 0      
    f.print out 
    f.truncate(f.pos)    
end 
+0

क्या लाखों लाइनों वाली बड़ी फाइलों के लिए दूसरा समाधान उपयुक्त है? क्या वह उस ऑपरेशन के लिए स्मृति में जगह नहीं लेगा? – mango

62

सामान्य में, वहाँ बीच में मनमाने ढंग से संपादन करने के लिए कोई रास्ता नहीं है एक फाइल का यह रूबी की कमी नहीं है। यह फ़ाइल सिस्टम की एक सीमा है: अधिकांश फ़ाइल सिस्टम अंत में फ़ाइल को बढ़ाने या घटाने के लिए आसान और कुशल बनाते हैं, लेकिन शुरुआत में या बीच में नहीं। तो जब तक इसका आकार वही रहता है तब तक आप एक पंक्ति को फिर से लिखने में सक्षम नहीं होंगे।

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

path = '/tmp/foo' 
lines = IO.readlines(path).map do |line| 
    'Kilroy was here ' + line 
end 
File.open(path, 'w') do |file| 
    file.puts lines 
end 

हालांकि सरल, इस तकनीक एक खतरा है: यदि प्रोग्राम फ़ाइल लेखन में बाधा आती है, तो आप हिस्सा खो देंगे या यह सब। इसे पूरी फ़ाइल को पकड़ने के लिए स्मृति का उपयोग करने की भी आवश्यकता है। यदि इनमें से कोई चिंता का विषय है, तो आप अगली तकनीक पसंद कर सकते हैं।

जैसा कि आप नोट करते हैं, एक अस्थायी फ़ाइल को लिख सकते हैं।ऐसा करने पर, अस्थायी फ़ाइल का नाम बदलें ताकि यह इनपुट फ़ाइल को बदल देता है:

require 'tempfile' 
require 'fileutils' 

path = '/tmp/foo' 
temp_file = Tempfile.new('foo') 
begin 
    File.open(path, 'r') do |file| 
    file.each_line do |line| 
     temp_file.puts 'Kilroy was here ' + line 
    end 
    end 
    temp_file.close 
    FileUtils.mv(temp_file.path, path) 
ensure 
    temp_file.close 
    temp_file.unlink 
end 

के बाद से नाम बदलने (FileUtils.mv) परमाणु है, फिर से लिखा इनपुट फ़ाइल सभी को एक बार अस्तित्व में ही पॉप करेगा। यदि प्रोग्राम बाधित है, तो फ़ाइल को फिर से लिखा जाएगा, या यह नहीं होगा। आंशिक रूप से फिर से लिखा जाने की कोई संभावना नहीं है।

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

+1

+1 फ़ाइलों को संशोधित करते समय रूढ़िवादी होना हमेशा बेहतर होता है, खासकर बड़े। –

+0

आप temp_file को बंद करने वाले हैं, इसे क्यों रिवाइंड करें? – hihell

+0

@hihell, BookOfGreg के संपादन ने रिवाइंड जोड़ा; उनकी टिप्पणी थी: "FileUtils.mv एक रिक्त फ़ाइल तब तक लिखेगा जब तक अस्थायी फ़ाइल रिवाउंड न हो। इसके अलावा यह सुनिश्चित करना सर्वोत्तम है कि अस्थायी फ़ाइल बंद हो और उपयोग के बाद अनलिंक हो।" –

1

बस मामले में आप रेल या Facets का उपयोग कर रहे हैं, या आप अन्यथा रेल 'ActiveSupport पर निर्भर करते हैं, तो आप उपयोग कर सकते हैं atomic_write विस्तार File रहे हैं:

File.atomic_write('path/file') do |file| 
    file.write('your content') 
end 

परदे के पीछे, यह एक अस्थायी फ़ाइल बनाने जाएगा जो बाद में यह आपके लिए फाइल बंद करने की देखभाल करने के लिए वांछित पथ पर चलेगा।

यह मौजूदा फ़ाइल की फ़ाइल अनुमतियों को आगे बढ़ाता है, या यदि वर्तमान निर्देशिका में से कोई नहीं है।

0

आप फ़ाइल के बीच में लिख सकते हैं लेकिन आपको स्ट्रिंग की लंबाई को रखने के लिए सावधान रहना होगा, अन्यथा आप निम्न में से कुछ पाठ को ओवरराइट करते हैं। मैं File.seek, IO :: SEEK_CUR का उपयोग करके यहां एक उदाहरण देता हूं, वह लाइन पॉइंटर की वर्तमान स्थिति देता है, जो लाइन को पढ़ने के अंत में है, +1 लाइन के अंत में सीआर चरित्र के लिए है।

look_for  = "bbb" 
replace_with = "xxxxx" 

File.open(DATA, 'r+') do |file| 
    file.each_line do |line| 
    if (line[look_for]) 
     file.seek(-(line.length + 1), IO::SEEK_CUR) 
     file.write line.gsub(look_for, replace_with) 
    end 
    end 
end 
__END__ 
aaabbb 
bbbcccddd 
dddeee 
eee 

निष्पादित करने के बाद, स्क्रिप्ट के अंत में अब आपके पास निम्नलिखित है, जो आपके मन में नहीं था, मुझे लगता है। 'पढ़ सकते हैं और एक नया फ़ाइल पर लिखने' विधि

aaaxxxxx 
bcccddd 
dddeee 
eee 

कि विचार में लेते हुए इस तकनीक का इस्तेमाल गति क्लासिक तुलना में काफी बेहतर है। 1.7 जीबी बड़े के संगीत डेटा वाले फ़ाइल पर इन बेंचमार्क देखें। क्लासिक दृष्टिकोण के लिए मैंने वेन की तकनीक का उपयोग किया। बेंचमार्क को .bmbm विधि को ठीक किया जाता है ताकि फ़ाइल का कैशिंग बहुत बड़ा सौदा न हो। विंडोज 7 पर एमआरआई रूबी 2.3.0 के साथ टेस्ट किए जाते हैं। स्ट्रिंग्स को प्रभावी ढंग से बदल दिया गया, मैंने दोनों विधियों की जांच की।

require 'benchmark' 
require 'tempfile' 
require 'fileutils' 

look_for  = "Melissa Etheridge" 
replace_with = "Malissa Etheridge" 
very_big_file = 'D:\Documents\muziekinfo\all.txt'.gsub('\\','/') 

def replace_with file_path, look_for, replace_with 
    File.open(file_path, 'r+') do |file| 
    file.each_line do |line| 
     if (line[look_for]) 
     file.seek(-(line.length + 1), IO::SEEK_CUR) 
     file.write line.gsub(look_for, replace_with) 
     end 
    end 
    end 
end 

def replace_with_classic path, look_for, replace_with 
    temp_file = Tempfile.new('foo') 
    File.foreach(path) do |line| 
    if (line[look_for]) 
     temp_file.write line.gsub(look_for, replace_with) 
    else 
     temp_file.write line 
    end 
    end 
    temp_file.close 
    FileUtils.mv(temp_file.path, path) 
ensure 
    temp_file.close 
    temp_file.unlink 
end 

Benchmark.bmbm do |x| 
    x.report("adapt   ") { 1.times {replace_with very_big_file, look_for, replace_with}} 
    x.report("restore  ") { 1.times {replace_with very_big_file, replace_with, look_for}} 
    x.report("classic adapt ") { 1.times {replace_with_classic very_big_file, look_for, replace_with}} 
    x.report("classic restore") { 1.times {replace_with_classic very_big_file, replace_with, look_for}} 
end 

कौन सा

Rehearsal --------------------------------------------------- 
adapt    6.989000 0.811000 7.800000 ( 7.800598) 
restore   7.192000 0.562000 7.754000 ( 7.774481) 
classic adapt 14.320000 9.438000 23.758000 (32.507433) 
classic restore 14.259000 9.469000 23.728000 (34.128093) 
----------------------------------------- total: 63.040000sec 

         user  system  total  real 
adapt    7.114000 0.718000 7.832000 ( 8.639864) 
restore   6.942000 0.858000 7.800000 ( 8.117839) 
classic adapt 14.430000 9.485000 23.915000 (32.195298) 
classic restore 14.695000 9.360000 24.055000 (33.709054) 

दिया तो in_file प्रतिस्थापन 4 गुना तेजी से किया गया था।