Tôi đang tìm một tập lệnh để tìm kiếm một tệp (hoặc danh sách các tệp) cho một mẫu và nếu tìm thấy, hãy thay thế mẫu đó bằng một giá trị nhất định.
Suy nghĩ?
Tôi đang tìm một tập lệnh để tìm kiếm một tệp (hoặc danh sách các tệp) cho một mẫu và nếu tìm thấy, hãy thay thế mẫu đó bằng một giá trị nhất định.
Suy nghĩ?
Câu trả lời:
Tuyên bố từ chối trách nhiệm: Cách tiếp cận này là một minh họa ngây thơ về khả năng của Ruby, và không phải là giải pháp cấp sản xuất để thay thế các chuỗi trong tệp. Nó dễ xảy ra các tình huống lỗi khác nhau, chẳng hạn như mất dữ liệu trong trường hợp sự cố, gián đoạn hoặc đĩa đầy. Mã này không phù hợp với bất kỳ thứ gì ngoài tập lệnh một lần nhanh chóng nơi tất cả dữ liệu được sao lưu. Vì lý do đó, KHÔNG sao chép mã này vào các chương trình của bạn.
Đây là một cách ngắn gọn để làm điều đó.
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")
Trên thực tế, Ruby có một tính năng chỉnh sửa tại chỗ. Giống như Perl, bạn có thể nói
ruby -pi.bak -e "gsub(/oldtext/, 'newtext')" *.txt
Điều này sẽ áp dụng mã trong dấu ngoặc kép cho tất cả các tệp trong thư mục hiện tại có tên kết thúc bằng ".txt". Bản sao lưu của các tệp đã chỉnh sửa sẽ được tạo bằng phần mở rộng ".bak" (Tôi nghĩ là "foobar.txt.bak").
LƯU Ý: điều này dường như không hoạt động đối với các tìm kiếm nhiều dòng. Đối với những thứ đó, bạn phải làm theo cách khác ít đẹp hơn, với một script wrapper xung quanh regex.
<main>': undefined method
gsub' cho chính: Object (NoMethodError)
-i
chỉnh sửa tại chỗ. .bak
là phần mở rộng được sử dụng cho tệp sao lưu (tùy chọn). -p
là một cái gì đó giống như while gets; <script>; puts $_; end
. ($_
là dòng được đọc cuối cùng, nhưng bạn có thể gán cho nó cho một cái gì đó như thế echo aa | ruby -p -e '$_.upcase!'
.)
Hãy nhớ rằng, khi bạn làm điều này, hệ thống tệp có thể hết dung lượng và bạn có thể tạo tệp có độ dài bằng không. Điều này thật thảm khốc nếu bạn đang làm điều gì đó như viết ra các tệp / etc / passwd như một phần của quản lý cấu hình hệ thống.
Lưu ý rằng chỉnh sửa tệp tại chỗ như trong câu trả lời được chấp nhận sẽ luôn cắt bớt tệp và ghi ra tệp mới theo trình tự. Sẽ luôn có một điều kiện chạy đua trong đó người đọc đồng thời sẽ thấy một tệp bị cắt ngắn. Nếu quá trình bị hủy bỏ vì bất kỳ lý do gì (ctrl-c, OOM kill, sự cố hệ thống, mất điện, v.v.) trong khi ghi thì tệp bị cắt cũng sẽ còn sót lại, điều này có thể rất thảm khốc. Đây là loại kịch bản dữ liệu mà các nhà phát triển PHẢI cân nhắc vì nó sẽ xảy ra. Vì lý do đó, tôi nghĩ câu trả lời được chấp nhận rất có thể không phải là câu trả lời được chấp nhận. Ở mức tối thiểu, hãy ghi vào một tệp tạm thời và di chuyển / đổi tên tệp vào vị trí giống như giải pháp "đơn giản" ở cuối câu trả lời này.
Bạn cần sử dụng một thuật toán:
Đọc tệp cũ và ghi ra tệp mới. (Bạn cần phải cẩn thận về việc chuyển toàn bộ tệp vào bộ nhớ).
Đóng tệp tạm thời mới một cách rõ ràng, đây là nơi bạn có thể ném một ngoại lệ vì bộ đệm tệp không thể được ghi vào đĩa vì không có dung lượng. (Nắm bắt điều này và dọn dẹp tệp tạm thời nếu bạn muốn, nhưng bạn cần phải ném lại thứ gì đó hoặc thất bại khá khó khăn vào thời điểm này.
Sửa các quyền và chế độ tệp trên tệp mới.
Đổi tên tệp mới và đặt nó vào vị trí.
Với hệ thống tệp ext3, bạn được đảm bảo rằng siêu dữ liệu ghi để di chuyển tệp vào đúng vị trí sẽ không bị hệ thống tệp sắp xếp lại và được ghi trước khi bộ đệm dữ liệu cho tệp mới được ghi, vì vậy điều này sẽ thành công hoặc không thành công. Hệ thống tệp ext4 cũng đã được vá để hỗ trợ loại hành vi này. Nếu bạn rất hoang tưởng, bạn nên gọi lệnh gọi fdatasync()
hệ thống như là bước 3.5 trước khi chuyển tệp vào vị trí.
Bất kể ngôn ngữ, đây là phương pháp hay nhất. Trong các ngôn ngữ mà việc gọi close()
không ném ra ngoại lệ (Perl hoặc C), bạn phải kiểm tra rõ ràng việc trả về close()
và ném ngoại lệ nếu nó không thành công.
Đề xuất ở trên chỉ cần chuyển tệp vào bộ nhớ, thao tác và ghi tệp ra tệp sẽ được đảm bảo tạo ra các tệp có độ dài bằng 0 trên hệ thống tệp đầy đủ. Bạn cần luôn sử dụng FileUtils.mv
để di chuyển một tệp tạm thời được viết đầy đủ vào vị trí.
Cân nhắc cuối cùng là vị trí của tệp tạm thời. Nếu bạn mở một tệp trong / tmp thì bạn phải xem xét một số vấn đề:
Nếu / tmp được gắn kết trên một hệ thống tệp khác, bạn có thể chạy / tmp hết dung lượng trước khi bạn ghi ra tệp mà nếu không thì tệp có thể triển khai đến đích của tệp cũ.
Có lẽ quan trọng hơn, khi bạn cố gắng chuyển mv
đổi tệp trên một thiết bị gắn kết, bạn sẽ được chuyển đổi thànhcp
hành vi một cách rõ ràng. Tệp cũ sẽ được mở, các tệp cũ inode sẽ được giữ nguyên và mở lại và nội dung tệp sẽ được sao chép. Đây rất có thể không phải là điều bạn muốn và bạn có thể gặp phải lỗi "tệp văn bản bận" nếu bạn cố gắng chỉnh sửa nội dung của tệp đang chạy. Điều này cũng làm mất đi mục đích của việc sử dụng các mv
lệnh hệ thống tệp và bạn có thể chạy hệ thống tệp đích hết dung lượng chỉ với một tệp được ghi một phần.
Điều này cũng không liên quan gì đến việc triển khai của Ruby. Hệ thống mv
và cp
các lệnh hoạt động tương tự.
Điều thích hợp hơn là mở Tempfile trong cùng thư mục với tệp cũ. Điều này đảm bảo rằng sẽ không có vấn đề di chuyển giữa các thiết bị. Cácmv
thân nó không bao giờ bị lỗi, và bạn sẽ luôn nhận được một tệp hoàn chỉnh và không bị xử lý. Bất kỳ lỗi nào, chẳng hạn như thiết bị hết dung lượng, lỗi quyền, v.v., sẽ gặp phải trong quá trình ghi Tempfile ra ngoài.
Nhược điểm duy nhất của phương pháp tạo Tempfile trong thư mục đích là:
Đây là một số mã triển khai thuật toán đầy đủ (mã windows chưa được kiểm tra và chưa hoàn thành):
#!/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")
Và đây là một phiên bản chặt chẽ hơn một chút không lo lắng về mọi trường hợp cạnh có thể xảy ra (nếu bạn đang sử dụng Unix và không quan tâm đến việc ghi vào / 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")
Trường hợp sử dụng thực sự đơn giản, khi bạn không quan tâm đến quyền của hệ thống tệp (bạn không chạy với quyền root hoặc bạn đang chạy với quyền root và tệp thuộc quyền sở hữu của root):
#!/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 : Tối thiểu phải được sử dụng thay cho câu trả lời được chấp nhận, trong mọi trường hợp, để đảm bảo bản cập nhật là nguyên tử và người đọc đồng thời sẽ không thấy các tệp bị cắt bớt. Như tôi đã đề cập ở trên, việc tạo Tempfile trong cùng thư mục với tệp đã chỉnh sửa là điều quan trọng ở đây để tránh các hoạt động mv thiết bị chéo được dịch thành các hoạt động cp nếu / tmp được gắn trên một thiết bị khác. Gọi fdatasync là một lớp hoang tưởng bổ sung, nhưng nó sẽ gây ảnh hưởng đến hiệu suất, vì vậy tôi đã bỏ qua nó khỏi ví dụ này vì nó không thường được thực hành.
Thực sự không có cách nào để chỉnh sửa tệp tại chỗ. Những gì bạn thường làm khi có thể thoát khỏi nó (tức là nếu tệp không quá lớn) là bạn đọc tệp vào bộ nhớ ( File.read
), thực hiện các thay thế của bạn trên chuỗi đọc ( String#gsub
) và sau đó ghi chuỗi đã thay đổi trở lại tệp ( File.open
, File#write
).
Nếu các tập tin đủ lớn cho rằng không khả thi, những gì bạn cần phải làm là đọc các tập tin trong khối (nếu mô hình bạn muốn thay thế sẽ không span nhiều dòng sau đó một đoạn thường có nghĩa là một dòng - bạn có thể sử dụng File.foreach
để đọc từng dòng một của tệp), và đối với mỗi đoạn, hãy thực hiện thay thế tệp đó và nối tệp đó vào tệp tạm thời. Khi bạn hoàn tất việc lặp lại tệp nguồn, bạn đóng nó và sử dụng FileUtils.mv
để ghi đè nó bằng tệp tạm thời.
Một cách tiếp cận khác là sử dụng chỉnh sửa tại chỗ bên trong Ruby (không phải từ dòng lệnh):
#!/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
Nếu bạn không muốn tạo bản sao lưu thì hãy thay đổi '.bak'
thành ''
.
read
) tệp. Nó có thể mở rộng và sẽ rất nhanh.
Điều này phù hợp với tôi:
filename = "foo"
text = File.read(filename)
content = text.gsub(/search_regexp/, "replacestring")
File.open(filename, "w") { |file| file << content }
Đây là một giải pháp để tìm / thay thế trong tất cả các tệp của một thư mục nhất định. Về cơ bản, tôi đã lấy câu trả lời do sepp2k cung cấp và mở rộng nó.
# 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) }
Nếu bạn cần thay thế trên các đường ranh giới, thì việc sử dụng ruby -pi -e
sẽ không hoạt động vì các p
quy trình xử lý từng dòng một. Thay vào đó, tôi khuyên bạn nên làm như sau, mặc dù nó có thể không thành công với tệp nhiều GB:
ruby -e "file='translation.ja.yml'; IO.write(file, (IO.read(file).gsub(/\s+'$/, %q('))))"
Tìm kiếm khoảng trắng (có thể bao gồm các dòng mới) theo sau một câu trích dẫn, trong trường hợp đó, nó sẽ loại bỏ khoảng trắng. Đây %q(')
chỉ là một cách trích dẫn lạ mắt của nhân vật trích dẫn.
Đây là một giải pháp thay thế cho một lớp lót từ jim, lần này là trong một tập lệnh
ARGV[0..-3].each{|f| File.write(f, File.read(f).gsub(ARGV[-2],ARGV[-1]))}
Lưu nó trong một script, ví dụ như Replace.rb
Bạn bắt đầu trên dòng lệnh với
replace.rb *.txt <string_to_replace> <replacement>
* .txt có thể được thay thế bằng một lựa chọn khác hoặc bằng một số tên tệp hoặc đường dẫn
được chia nhỏ để tôi có thể giải thích những gì đang xảy ra nhưng vẫn có thể thực thi
# 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
CHỈNH SỬA: nếu bạn muốn sử dụng biểu thức chính quy, hãy sử dụng biểu thức này để thay thế Rõ ràng, điều này chỉ để xử lý các tệp văn bản tương đối nhỏ, không có quái vật Gigabyte
ARGV[0..-3].each{|f| File.write(f, File.read(f).gsub(/#{ARGV[-2]}/,ARGV[-1]))}
File.read
cần được xem xét với thông tin trong stackoverflow.com/a/25189286/128421 để biết lý do tại sao việc tải tệp lớn lại có hại. Ngoài ra, thay vìFile.open(filename, "w") { |file| file << content }
sử dụng các biến thểFile.write(filename, content)
.