ฉันกำลังมองหาสคริปต์เพื่อค้นหาไฟล์ (หรือรายการไฟล์) สำหรับรูปแบบและหากพบให้แทนที่รูปแบบนั้นด้วยค่าที่กำหนด
คิด?
ฉันกำลังมองหาสคริปต์เพื่อค้นหาไฟล์ (หรือรายการไฟล์) สำหรับรูปแบบและหากพบให้แทนที่รูปแบบนั้นด้วยค่าที่กำหนด
คิด?
คำตอบ:
ข้อจำกัดความรับผิดชอบ: วิธีนี้เป็นภาพประกอบที่ไร้เดียงสาของความสามารถของ Ruby และไม่ใช่โซลูชันระดับการผลิตสำหรับการแทนที่สตริงในไฟล์ มีแนวโน้มที่จะเกิดความล้มเหลวในสถานการณ์ต่างๆเช่นข้อมูลสูญหายในกรณีที่เกิดข้อผิดพลาดขัดจังหวะหรือดิสก์เต็ม รหัสนี้ไม่เหมาะสำหรับสิ่งอื่นใดนอกเหนือไปจากสคริปต์ด่วนเพียงครั้งเดียวที่สำรองข้อมูลทั้งหมดไว้ ด้วยเหตุนี้อย่าคัดลอกรหัสนี้ลงในโปรแกรมของคุณ
นี่เป็นวิธีง่ายๆสั้น ๆ ในการทำ
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 มีคุณสมบัติการแก้ไขแบบแทนที่ เช่นเดียวกับ Perl คุณสามารถพูดได้
ruby -pi.bak -e "gsub(/oldtext/, 'newtext')" *.txt
การดำเนินการนี้จะใช้รหัสในเครื่องหมายอัญประกาศคู่กับไฟล์ทั้งหมดในไดเรกทอรีปัจจุบันที่มีชื่อลงท้ายด้วย ".txt" สำเนาสำรองของไฟล์ที่แก้ไขจะถูกสร้างขึ้นด้วยนามสกุล ".bak" (ฉันคิดว่า "foobar.txt.bak")
หมายเหตุ: ดูเหมือนว่าสิ่งนี้จะใช้ไม่ได้กับการค้นหาแบบหลายบรรทัด สำหรับสิ่งนั้นคุณต้องทำอีกวิธีที่ไม่ค่อยสวยด้วยสคริปต์ wrapper รอบ regex
<main>': undefined method
gsub 'สำหรับ main: Object (NoMethodError)
-i
แก้ไขในสถานที่. .bak
เป็นส่วนขยายที่ใช้สำหรับไฟล์สำรอง (ทางเลือก) เป็นสิ่งที่ต้องการ-p
while gets; <script>; puts $_; end
( $_
เป็นบรรทัดการอ่านสุดท้าย แต่คุณสามารถกำหนดให้เป็นบางอย่างecho aa | ruby -p -e '$_.upcase!'
ได้)
โปรดทราบว่าเมื่อคุณทำเช่นนี้ระบบไฟล์อาจไม่มีพื้นที่เหลือและคุณอาจสร้างไฟล์ที่มีความยาวเป็นศูนย์ นี่เป็นหายนะหากคุณกำลังทำบางอย่างเช่นการเขียนไฟล์ / etc / passwd เป็นส่วนหนึ่งของการจัดการการกำหนดค่าระบบ
โปรดทราบว่าการแก้ไขไฟล์ในสถานที่เช่นในคำตอบที่ยอมรับจะตัดทอนไฟล์และเขียนไฟล์ใหม่ตามลำดับเสมอ มักจะมีเงื่อนไขการแข่งขันที่ผู้อ่านพร้อมกันจะเห็นไฟล์ที่ถูกตัดทอน หากกระบวนการถูกยกเลิกไม่ว่าด้วยเหตุผลใด ๆ (ctrl-c, OOM killer, ระบบขัดข้อง, ไฟดับ ฯลฯ ) ระหว่างการเขียนไฟล์ที่ถูกตัดทอนก็จะถูกทิ้งไว้ซึ่งอาจเป็นภัยพิบัติได้ นี่คือสถานการณ์จำลอง dataloss ที่นักพัฒนาต้องพิจารณาเพราะมันจะเกิดขึ้น ด้วยเหตุนี้ฉันจึงคิดว่าคำตอบที่ได้รับการยอมรับน่าจะไม่ใช่คำตอบที่ยอมรับ อย่างน้อยที่สุดให้เขียนลงใน tempfile และย้าย / เปลี่ยนชื่อไฟล์ให้เข้าที่เหมือนคำตอบ "ง่ายๆ" ที่ท้ายคำตอบนี้
คุณต้องใช้อัลกอริทึมที่:
อ่านไฟล์เก่าและเขียนไปยังไฟล์ใหม่ (คุณต้องระวังเกี่ยวกับการแปลงไฟล์ทั้งหมดในหน่วยความจำ)
ปิดไฟล์ชั่วคราวใหม่อย่างชัดเจนซึ่งเป็นที่ที่คุณอาจทิ้งข้อยกเว้นเนื่องจากไม่สามารถเขียนบัฟเฟอร์ไฟล์ลงในดิสก์ได้เนื่องจากไม่มีที่ว่าง (จับสิ่งนี้และล้างไฟล์ชั่วคราวหากคุณต้องการ แต่คุณจำเป็นต้องสร้างบางสิ่งใหม่หรือล้มเหลวในตอนนี้
แก้ไขการอนุญาตไฟล์และโหมดในไฟล์ใหม่
เปลี่ยนชื่อไฟล์ใหม่และวางลงในตำแหน่ง
ด้วยระบบไฟล์ ext3 คุณรับประกันได้ว่าการเขียนข้อมูลเมตาเพื่อย้ายไฟล์เข้าที่จะไม่ถูกจัดเรียงใหม่โดยระบบไฟล์และเขียนก่อนที่จะเขียนบัฟเฟอร์ข้อมูลสำหรับไฟล์ใหม่ดังนั้นสิ่งนี้ควรสำเร็จหรือล้มเหลว ระบบไฟล์ ext4 ยังได้รับการแก้ไขเพื่อรองรับลักษณะการทำงานประเภทนี้ หากคุณหวาดระแวงมากควรเรียกfdatasync()
ระบบเรียกตามขั้นตอน 3.5 ก่อนที่จะย้ายไฟล์เข้าที่
นี่คือแนวทางปฏิบัติที่ดีที่สุดโดยไม่คำนึงถึงภาษา ในภาษาที่การโทรclose()
ไม่ทำให้เกิดข้อยกเว้น (Perl หรือ C) คุณต้องตรวจสอบการส่งคืนclose()
ข้อยกเว้นอย่างชัดเจนหากล้มเหลว
คำแนะนำข้างต้นเพียงแค่ทำการแปลงไฟล์ลงในหน่วยความจำจัดการไฟล์และเขียนลงในไฟล์จะรับประกันได้ว่าจะสร้างไฟล์ที่มีความยาวเป็นศูนย์บนระบบไฟล์แบบเต็ม คุณจำเป็นต้องเสมอใช้FileUtils.mv
ที่จะย้ายไฟล์ชั่วคราวอย่างเต็มที่เขียนเข้าไปในสถานที่
การพิจารณาขั้นสุดท้ายคือการจัดวางไฟล์ชั่วคราว หากคุณเปิดไฟล์ใน / tmp คุณต้องพิจารณาปัญหาเล็กน้อย:
หากติดตั้ง / tmp บนระบบไฟล์อื่นคุณอาจเรียกใช้ / tmp ไม่เพียงพอก่อนที่คุณจะเขียนไฟล์ที่จะนำไปใช้กับปลายทางของไฟล์เก่าได้
อาจสำคัญกว่านั้นเมื่อคุณพยายามที่จะเชื่อมmv
ต่อไฟล์ผ่านอุปกรณ์เมาท์คุณจะได้รับการแปลงcp
พฤติกรรมอย่างโปร่งใส ไฟล์เก่าจะถูกเปิดขึ้นไอโหนดไฟล์เก่าจะถูกเก็บรักษาและเปิดขึ้นมาใหม่และเนื้อหาของไฟล์จะถูกคัดลอก ซึ่งส่วนใหญ่จะไม่ใช่สิ่งที่คุณต้องการและคุณอาจพบข้อผิดพลาด "ไฟล์ข้อความไม่ว่าง" หากคุณพยายามแก้ไขเนื้อหาของไฟล์ที่กำลังทำงานอยู่ นอกจากนี้ยังเอาชนะวัตถุประสงค์ของการใช้mv
คำสั่งระบบไฟล์และคุณอาจเรียกใช้ระบบไฟล์ปลายทางโดยไม่ใช้พื้นที่โดยมีไฟล์ที่เขียนเพียงบางส่วน
สิ่งนี้ไม่มีส่วนเกี่ยวข้องกับการนำไปใช้ของ Ruby ระบบmv
และcp
คำสั่งทำงานคล้ายกัน
สิ่งที่ดีกว่าคือเปิด Tempfile ในไดเร็กทอรีเดียวกับไฟล์เก่า เพื่อให้แน่ใจว่าจะไม่มีปัญหาการย้ายข้ามอุปกรณ์ mv
ตัวเองไม่ควรล้มเหลวและคุณก็ควรจะได้รับไฟล์ที่สมบูรณ์และ untruncated ควรพบข้อผิดพลาดใด ๆ เช่นอุปกรณ์ไม่มีพื้นที่ว่างข้อผิดพลาดการอนุญาต ฯลฯ ในระหว่างการเขียน Tempfile out
ข้อเสียเพียงประการเดียวของวิธีการสร้าง Tempfile ในไดเร็กทอรีปลายทางคือ:
นี่คือรหัสบางส่วนที่ใช้อัลกอริทึมแบบเต็ม (รหัส windows ยังไม่ทดสอบและยังไม่เสร็จ):
#!/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")
และนี่คือเวอร์ชันที่เข้มงวดกว่าเล็กน้อยซึ่งไม่ต้องกังวลกับทุกกรณีขอบที่เป็นไปได้ (หากคุณใช้ Unix และไม่สนใจที่จะเขียนถึง / proc):
#!/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")
TL; DR : ควรใช้แทนคำตอบที่ยอมรับเป็นอย่างน้อยในทุกกรณีเพื่อให้แน่ใจว่าการอัปเดตเป็นแบบอะตอมและโปรแกรมอ่านพร้อมกันจะไม่เห็นไฟล์ที่ถูกตัดทอน ดังที่ฉันได้กล่าวไว้ข้างต้นการสร้าง Tempfile ในไดเร็กทอรีเดียวกันกับไฟล์ที่แก้ไขเป็นสิ่งสำคัญที่นี่เพื่อหลีกเลี่ยงการดำเนินการ mv ข้ามอุปกรณ์ที่ถูกแปลเป็นการดำเนินการ cp หาก / tmp ติดตั้งบนอุปกรณ์อื่น การเรียก fdatasync เป็นการเพิ่มชั้นของความหวาดระแวง แต่มันจะได้รับผลกระทบด้านประสิทธิภาพดังนั้นฉันจึงละเว้นจากตัวอย่างนี้เนื่องจากไม่ได้รับการฝึกฝนโดยทั่วไป
ไม่มีวิธีแก้ไขไฟล์ในสถานที่จริงๆ สิ่งที่คุณมักจะทำเมื่อคุณสามารถหลีกเลี่ยงมันได้ (เช่นถ้าไฟล์ไม่ใหญ่เกินไป) คือคุณอ่านไฟล์ลงในหน่วยความจำ ( File.read
) ทำการแทนที่ของคุณในสตริงการอ่าน ( String#gsub
) จากนั้นเขียนสตริงที่เปลี่ยนแปลงกลับไปที่ ไฟล์ ( File.open
, File#write
).
หากไฟล์มีขนาดใหญ่พอที่จะทำไม่ได้สิ่งที่คุณต้องทำคืออ่านไฟล์เป็นชิ้น ๆ (หากรูปแบบที่คุณต้องการแทนที่ไม่ครอบคลุมหลายบรรทัดจากนั้นหนึ่งกลุ่มมักจะหมายถึงหนึ่งบรรทัด - คุณสามารถใช้File.foreach
เพื่อ อ่านไฟล์ทีละบรรทัด) และสำหรับแต่ละกลุ่มให้ทำการแทนที่และต่อท้ายไฟล์ชั่วคราว เมื่อคุณทำซ้ำบนไฟล์ต้นฉบับเสร็จแล้วให้ปิดและใช้FileUtils.mv
เพื่อเขียนทับไฟล์ชั่วคราว
อีกวิธีหนึ่งคือการใช้การแก้ไขแบบแทนที่ภายใน Ruby (ไม่ใช่จากบรรทัดคำสั่ง):
#!/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
ประมวลผลทีละบรรทัด ฉันขอแนะนำสิ่งต่อไปนี้แทนแม้ว่าอาจล้มเหลวด้วยไฟล์หลาย GB:
ruby -e "file='translation.ja.yml'; IO.write(file, (IO.read(file).gsub(/\s+'$/, %q('))))"
กำลังมองหาพื้นที่สีขาว (อาจรวมถึงบรรทัดใหม่) ตามด้วยเครื่องหมายคำพูดซึ่งในกรณีนี้มันจะกำจัดช่องว่าง วิธี%q(')
นี้เป็นเพียงวิธีแฟนซีในการอ้างถึงอักขระเครื่องหมายคำพูด
นี่เป็นอีกทางเลือกหนึ่งของซับจาก jim คราวนี้เป็นสคริปต์
ARGV[0..-3].each{|f| File.write(f, File.read(f).gsub(ARGV[-2],ARGV[-1]))}
บันทึกไว้ในสคริปต์เช่น replace.rb
คุณเริ่มต้นในบรรทัดคำสั่งด้วย
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
แก้ไข: หากคุณต้องการใช้นิพจน์ทั่วไปให้ใช้สิ่งนี้แทนแน่นอนว่านี่ใช้สำหรับจัดการไฟล์ข้อความที่ค่อนข้างเล็กเท่านั้นไม่มีมอนสเตอร์ Gigabyte
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)
ต่างๆ