मैं एक पैटर्न के लिए एक फ़ाइल (या फ़ाइलों की सूची) की खोज करने के लिए एक स्क्रिप्ट की तलाश कर रहा हूं और यदि पाया जाता है, तो उस पैटर्न को दिए गए मान के साथ बदलें।
विचार?
मैं एक पैटर्न के लिए एक फ़ाइल (या फ़ाइलों की सूची) की खोज करने के लिए एक स्क्रिप्ट की तलाश कर रहा हूं और यदि पाया जाता है, तो उस पैटर्न को दिए गए मान के साथ बदलें।
विचार?
जवाबों:
डिस्क्लेमर: यह दृष्टिकोण रूबी की क्षमताओं का एक भोला चित्र है, और फाइलों में तारों को बदलने के लिए उत्पादन-ग्रेड समाधान नहीं है। यह विभिन्न विफलता परिदृश्यों के लिए प्रवण होता है, जैसे दुर्घटना, रुकावट, या डिस्क के पूर्ण होने की स्थिति में डेटा हानि। यह कोड एक त्वरित एक-बंद स्क्रिप्ट से परे किसी भी चीज के लिए फिट नहीं है, जहां सभी डेटा का बैकअप लिया जाता है। इस कारण से, इस कोड को अपने कार्यक्रमों में कॉपी न करें।
यहाँ यह करने के लिए एक त्वरित लघु तरीका है।
file_names = ['foo.txt', 'bar.txt']
file_names.each do |file_name|
text = File.read(file_name)
new_contents = text.gsub(/search_regexp/, "replacement string")
# To merely print the contents of the file, use:
puts new_contents
# To write changes to the file, use:
File.open(file_name, "w") {|file| file.puts new_contents }
end
File.write(file_name, text.gsub(/regexp/, "replace")
दरअसल, रूबी के पास इन-प्लेस एडिटिंग फीचर है। पर्ल की तरह, आप कह सकते हैं
ruby -pi.bak -e "gsub(/oldtext/, 'newtext')" *.txt
यह वर्तमान निर्देशिका में उन सभी फ़ाइलों के लिए दोहरे-उद्धरणों में कोड लागू करेगा, जिनके नाम ".txt" के साथ समाप्त होते हैं। संपादित फ़ाइलों की बैकअप प्रतियां ".bak" एक्सटेंशन ("foobar.txt.bak" मुझे लगता है) के साथ बनाई जाएंगी।
नोट: यह मल्टीलाइन खोजों के लिए काम नहीं करता है। उन लोगों के लिए, आपको इसे अन्य कम सुंदर तरीके से करना होगा, रेगेक्स के चारों ओर एक आवरण स्क्रिप्ट के साथ।
<main>': undefined method
gsub में मुख्य के लिए: ऑब्जेक्ट (NoMethodError)
-i
जगह-जगह संपादन करता है। .bak
बैकअप फ़ाइल (वैकल्पिक) के लिए उपयोग किया जाने वाला एक्सटेंशन है। -p
की तरह कुछ है while gets; <script>; puts $_; end
। ( $_
अंतिम पठन पंक्ति है, लेकिन आप इसे कुछ इस तरह से असाइन कर सकते हैं echo aa | ruby -p -e '$_.upcase!'
।)
ध्यान रखें कि, जब आप ऐसा करते हैं, तो फ़ाइल सिस्टम अंतरिक्ष से बाहर हो सकता है और आप एक शून्य-लंबाई फ़ाइल बना सकते हैं। यदि आप सिस्टम कॉन्फ़िगरेशन प्रबंधन के भाग के रूप में बाहर / etc / passwd फ़ाइलों को लिखने जैसा कुछ कर रहे हैं तो यह भयावह है।
ध्यान दें कि स्वीकार किए गए उत्तर की तरह इन-प्लेस फ़ाइल संपादन हमेशा फ़ाइल को छोटा करेगा और क्रमिक रूप से नई फ़ाइल लिखेगा। हमेशा एक दौड़ की स्थिति होगी जहां समवर्ती पाठकों को एक छंटनी की गई फ़ाइल दिखाई देगी। यदि लिखने के दौरान किसी भी कारण (ctrl-c, OOM हत्यारा, सिस्टम क्रैश, पावर आउटेज, आदि) के लिए प्रक्रिया को रद्द कर दिया जाता है, तो छंटनी की गई फ़ाइल को भी छोड़ दिया जाएगा, जो भयावह हो सकती है। यह एक प्रकार का डलाटॉस परिदृश्य है जिसे डेवलपर्स जरूरी मानते हैं क्योंकि यह होगा। उस कारण से, मुझे लगता है कि स्वीकृत उत्तर को स्वीकार किए जाने वाले उत्तर में नहीं होना चाहिए। एक नंगे न्यूनतम लिखने के लिए एक अस्थायी और इस उत्तर के अंत में "सरल" समाधान की तरह फ़ाइल को स्थानांतरित / स्थानांतरित करें।
आपको एक एल्गोरिथ्म का उपयोग करने की आवश्यकता है जो:
पुरानी फ़ाइल पढ़ता है और नई फ़ाइल को लिखता है। (आपको संपूर्ण फ़ाइलों को मेमोरी में खिसकने से सावधान रहने की आवश्यकता है)।
स्पष्ट रूप से नई अस्थायी फ़ाइल को बंद कर देता है, जो कि आप एक अपवाद फेंक सकते हैं क्योंकि फ़ाइल बफ़र्स को डिस्क पर नहीं लिखा जा सकता क्योंकि कोई स्थान नहीं है। (इसे पकड़ें और यदि आप चाहें, तो अस्थायी फ़ाइल को साफ़ करें, लेकिन आपको इस बिंदु पर कुछ पुनर्विचार करने या असफल होने की आवश्यकता है।
नई फ़ाइल पर फ़ाइल अनुमतियाँ और मोड को ठीक करता है।
नई फ़ाइल का नाम बदलता है और इसे जगह में छोड़ देता है।
Ext3 फाइल सिस्टम के साथ आपको गारंटी दी जाती है कि फ़ाइल को स्थानांतरित करने के लिए मेटाडेटा फ़ाइल सिस्टम द्वारा पुनर्व्यवस्थित नहीं होगा और नई फ़ाइल के लिए डेटा बफ़र्स लिखे जाने से पहले लिखा गया है, इसलिए यह या तो सफल होना चाहिए या विफल होना चाहिए। इस तरह के व्यवहार का समर्थन करने के लिए ext4 फाइलसिस्टम को भी पैच किया गया है। यदि आप बहुत अधिक पागल हैं, तो आपको fdatasync()
फ़ाइल को जगह में ले जाने से पहले चरण 3.5 के रूप में सिस्टम कॉल को कॉल करना चाहिए ।
भाषा के बावजूद, यह सबसे अच्छा अभ्यास है। उन भाषाओं में जहां कॉलिंग close()
एक अपवाद को नहीं फेंकती है (पर्ल या सी) आपको स्पष्ट रूप से वापसी की जांच करनी चाहिए close()
और अगर यह विफल हो जाता है तो अपवाद को फेंक दें।
ऊपर दिए गए सुझाव को फ़ाइल को केवल मेमोरी में खिसकाना, इसमें हेरफेर करना और इसे फ़ाइल में लिखना एक पूर्ण फाइलसिस्टम पर शून्य-लंबाई फ़ाइलों का उत्पादन करने की गारंटी होगी। आपको हमेशा उपयोग करने की आवश्यकता हैFileUtils.mv
एक पूरी तरह से लिखित अस्थायी फ़ाइल को जगह में स्थानांतरित करने के लिए करने है।
एक अंतिम विचार अस्थायी फ़ाइल की नियुक्ति है। यदि आप / tmp में एक फ़ाइल खोलते हैं तो आपको कुछ समस्याओं पर विचार करना होगा:
यदि / tmp किसी भिन्न फ़ाइल सिस्टम पर आरूढ़ है, तो इससे पहले कि आप उस फ़ाइल को लिखे, जिसे अन्यथा पुरानी फ़ाइल के गंतव्य के लिए लागू किया जाएगा।
संभवतः अधिक महत्वपूर्ण बात यह है कि जब आप mv
डिवाइस माउंट करने की कोशिश करेंगे तो आप पारदर्शी रूप से cp
व्यवहार में परिवर्तित हो जाएंगे । पुरानी फ़ाइल को खोला जाएगा, पुरानी फ़ाइलों को इनकोड संरक्षित किया जाएगा और फिर से खोला जाएगा और फ़ाइल सामग्री की प्रतिलिपि बनाई जाएगी। यह सबसे अधिक संभावना है कि आप क्या चाहते हैं, और आप "पाठ फ़ाइल व्यस्त" त्रुटियों में चला सकते हैं यदि आप किसी चल रही फ़ाइल की सामग्री को संपादित करने का प्रयास करते हैं। यह फाइलसिस्टम mv
कमांड का उपयोग करने के उद्देश्य को भी पराजित करता है और आप केवल आंशिक रूप से लिखित फाइल के साथ गंतव्य फाइल सिस्टम को अंतरिक्ष से बाहर चला सकते हैं।
यह भी रूबी के कार्यान्वयन से कोई लेना-देना नहीं है। सिस्टम mv
और cp
कमांड समान व्यवहार करते हैं।
पुरानी फ़ाइल के रूप में एक ही निर्देशिका में एक Tempfile खोलने के लिए अधिक बेहतर है। यह सुनिश्चित करता है कि कोई क्रॉस-डिवाइस चालित समस्याएं नहीं होंगी। mv
खुद असफल कभी नहीं करना चाहिए, और आप हमेशा एक पूर्ण और untruncated फ़ाइल मिलना चाहिए। किसी भी विफलताओं, जैसे कि अंतरिक्ष से बाहर उपकरण, अनुमति त्रुटियां, इत्यादि का सामना करना चाहिए, जो कि टेंपफाइल आउट लिखने के दौरान किया जाना चाहिए।
गंतव्य निर्देशिका में Tempfile बनाने के दृष्टिकोण में केवल निम्न हैं:
यहाँ कुछ कोड है जो पूर्ण-एल्गोरिथ्म को लागू करता है (विंडोज़ कोड अप्राप्त और अपूर्ण है):
#!/usr/bin/env ruby
require 'tempfile'
def file_edit(filename, regexp, replacement)
tempdir = File.dirname(filename)
tempprefix = File.basename(filename)
tempprefix.prepend('.') unless RUBY_PLATFORM =~ /mswin|mingw|windows/
tempfile =
begin
Tempfile.new(tempprefix, tempdir)
rescue
Tempfile.new(tempprefix)
end
File.open(filename).each do |line|
tempfile.puts line.gsub(regexp, replacement)
end
tempfile.fdatasync unless RUBY_PLATFORM =~ /mswin|mingw|windows/
tempfile.close
unless RUBY_PLATFORM =~ /mswin|mingw|windows/
stat = File.stat(filename)
FileUtils.chown stat.uid, stat.gid, tempfile.path
FileUtils.chmod stat.mode, tempfile.path
else
# FIXME: apply perms on windows
end
FileUtils.mv tempfile.path, filename
end
file_edit('/tmp/foo', /foo/, "baz")
और यहाँ एक थोड़ा तंग संस्करण है जो हर संभव मामले के बारे में चिंता नहीं करता है (यदि आप यूनिक्स पर हैं और लिखने / खरीदने की परवाह नहीं करते हैं:
#!/usr/bin/env ruby
require 'tempfile'
def file_edit(filename, regexp, replacement)
Tempfile.open(".#{File.basename(filename)}", File.dirname(filename)) do |tempfile|
File.open(filename).each do |line|
tempfile.puts line.gsub(regexp, replacement)
end
tempfile.fdatasync
tempfile.close
stat = File.stat(filename)
FileUtils.chown stat.uid, stat.gid, tempfile.path
FileUtils.chmod stat.mode, tempfile.path
FileUtils.mv tempfile.path, filename
end
end
file_edit('/tmp/foo', /foo/, "baz")
वास्तव में सरल उपयोग-मामला, जब आप फ़ाइल सिस्टम अनुमतियों के बारे में परवाह नहीं करते हैं (या तो आप रूट के रूप में नहीं चल रहे हैं, या आप रूट के रूप में चल रहे हैं और फ़ाइल रूट के स्वामित्व में है):
#!/usr/bin/env ruby
require 'tempfile'
def file_edit(filename, regexp, replacement)
Tempfile.open(".#{File.basename(filename)}", File.dirname(filename)) do |tempfile|
File.open(filename).each do |line|
tempfile.puts line.gsub(regexp, replacement)
end
tempfile.close
FileUtils.mv tempfile.path, filename
end
end
file_edit('/tmp/foo', /foo/, "baz")
टीएल; डीआर : इसका उपयोग कम से कम स्वीकृत उत्तर के बजाय सभी मामलों में किया जाना चाहिए, ताकि यह सुनिश्चित हो सके कि अद्यतन परमाणु है और समवर्ती पाठकों को छंटनी की गई फाइलें नहीं दिखाई देंगी। जैसा कि मैंने ऊपर उल्लेख किया है, संपादित फ़ाइल के रूप में एक ही निर्देशिका में Tempfile बनाना यहाँ क्रॉस डिवाइस mv संचालन cp संचालन में अनुवादित होने से बचने के लिए महत्वपूर्ण है अगर / tmp को किसी भिन्न डिवाइस पर आरोहित किया जाता है। Fdatasync को कॉल करना व्यामोह की एक अतिरिक्त परत है, लेकिन यह एक प्रदर्शन हिट को प्रभावित करेगा, इसलिए मैंने इसे इस उदाहरण से छोड़ दिया क्योंकि यह आमतौर पर प्रचलित नहीं है।
वास्तव में इन-फाइल्स को संपादित करने का कोई तरीका नहीं है। जब आप इसे दूर कर सकते हैं तो आप आमतौर पर क्या करते हैं (यानी यदि फाइलें बहुत बड़ी नहीं हैं), तो आप फ़ाइल को मेमोरी में पढ़ें ( File.read
), रीड स्ट्रिंग पर अपने प्रतिस्थापन का प्रदर्शन करें ( String#gsub
) और फिर बदले हुए स्ट्रिंग को वापस लिखें। फ़ाइल ( File.open
, File#write
)।
आप उपयोग कर सकते हैं - फ़ाइलें बहुत बड़ी है कि अव्यावहारिक होने के लिए, आप क्या करने की जरूरत है, तो मात्रा में फ़ाइल को पढ़ने (यदि पैटर्न आप को बदलने के लिए कई पंक्तियों अवधि नहीं होगा तो एक हिस्सा आमतौर पर एक पंक्ति का अर्थ है चाहता हूँ File.foreach
के लिए एक फ़ाइल लाइन को लाइन से पढ़ें), और प्रत्येक चंक के लिए उस पर प्रतिस्थापन का प्रदर्शन करें और इसे एक अस्थायी फ़ाइल में जोड़ें। जब आप स्रोत फ़ाइल पर पुनरावृत्ति कर रहे हैं, तो आप इसे बंद कर देते हैं और FileUtils.mv
अस्थायी फ़ाइल के साथ इसे अधिलेखित करने के लिए उपयोग करते हैं।
एक अन्य तरीका रूबी के अंदर इनकम एडिटिंग का उपयोग करना है (कमांड लाइन से नहीं):
#!/usr/bin/ruby
def inplace_edit(file, bak, &block)
old_stdout = $stdout
argf = ARGF.clone
argf.argv.replace [file]
argf.inplace_mode = bak
argf.each_line do |line|
yield line
end
argf.close
$stdout = old_stdout
end
inplace_edit 'test.txt', '.bak' do |line|
line = line.gsub(/search1/,"replace1")
line = line.gsub(/search2/,"replace2")
print line unless line.match(/something/)
end
यदि आप बैकअप नहीं बनाना चाहते हैं तो बदल '.bak'
दें ''
।
read
फ़ाइल को स्लूर ( ) करने की कोशिश करने से बेहतर होगा । यह स्केलेबल है और बहुत तेज होना चाहिए।
यह मेरे लिए काम करता है:
filename = "foo"
text = File.read(filename)
content = text.gsub(/search_regexp/, "replacestring")
File.open(filename, "w") { |file| file << content }
यहां दिए गए डायरेक्टरी की सभी फाइलों को खोजने / बदलने का एक समाधान है। मूल रूप से मैंने sepp2k द्वारा प्रदान किया गया उत्तर लिया और इसका विस्तार किया।
# First set the files to search/replace in
files = Dir.glob("/PATH/*")
# Then set the variables for find/replace
@original_string_or_regex = /REGEX/
@replacement_string = "STRING"
files.each do |file_name|
text = File.read(file_name)
replace = text.gsub!(@original_string_or_regex, @replacement_string)
File.open(file_name, "w") { |file| file.puts replace }
end
require 'trollop'
opts = Trollop::options do
opt :output, "Output file", :type => String
opt :input, "Input file", :type => String
opt :ss, "String to search", :type => String
opt :rs, "String to replace", :type => String
end
text = File.read(opts.input)
text.gsub!(opts.ss, opts.rs)
File.open(opts.output, 'w') { |f| f.write(text) }
यदि आपको लाइन सीमाओं के पार प्रतिस्थापन करने की आवश्यकता है, तो उपयोग करने ruby -pi -e
से काम नहीं चलेगा क्योंकि p
प्रक्रियाएं एक बार में एक पंक्ति बनाती हैं। इसके बजाय, मैं निम्नलिखित की सिफारिश करता हूं, हालांकि यह बहु-जीबी फ़ाइल के साथ विफल हो सकता है:
ruby -e "file='translation.ja.yml'; IO.write(file, (IO.read(file).gsub(/\s+'$/, %q('))))"
एक उद्धरण के बाद सफेद स्थान (नई लाइनों सहित संभावित रूप से) की तलाश की जाती है, इस स्थिति में यह व्हाट्सएप से छुटकारा दिलाता है। %q(')
उद्धरण कैरेक्टर के हवाले से सिर्फ एक फैंसी तरीका है।
यहाँ एक लिपि से एक लाइनर का विकल्प, इस बार एक स्क्रिप्ट में
ARGV[0..-3].each{|f| File.write(f, File.read(f).gsub(ARGV[-2],ARGV[-1]))}
इसे स्क्रिप्ट में सेव करें, जैसे रिप्लेस.आरबी
आप कमांड लाइन पर शुरू करते हैं
replace.rb *.txt <string_to_replace> <replacement>
* .txt को दूसरे चयन के साथ या कुछ फ़ाइलनाम या रास्तों से बदला जा सकता है
टूट गया ताकि मैं समझा सकूं कि क्या हो रहा है लेकिन अभी भी निष्पादन योग्य है
# ARGV is an array of the arguments passed to the script.
ARGV[0..-3].each do |f| # enumerate the arguments of this script from the first to the last (-1) minus 2
File.write(f, # open the argument (= filename) for writing
File.read(f) # open the argument (= filename) for reading
.gsub(ARGV[-2],ARGV[-1])) # and replace all occurances of the beforelast with the last argument (string)
end
संपादित करें: यदि आप इसके बजाय एक नियमित अभिव्यक्ति का उपयोग करना चाहते हैं, तो जाहिर है, यह केवल अपेक्षाकृत छोटी पाठ फ़ाइलों को संभालने के लिए है, किसी भी विषयगत राक्षस के लिए नहीं
ARGV[0..-3].each{|f| File.write(f, File.read(f).gsub(/#{ARGV[-2]}/,ARGV[-1]))}
File.read
करने के लिए stackoverflow.com/a/25189286/128421 में जानकारी के साथ उपयोग करने की आवश्यकता है। इसके अलावा,File.open(filename, "w") { |file| file << content }
बदलाव के बजाय का उपयोग करेंFile.write(filename, content)
।