Ich suche nach einem Skript, um eine Datei (oder eine Liste von Dateien) nach einem Muster zu durchsuchen und, falls gefunden, dieses Muster durch einen bestimmten Wert zu ersetzen.
Gedanken?
Ich suche nach einem Skript, um eine Datei (oder eine Liste von Dateien) nach einem Muster zu durchsuchen und, falls gefunden, dieses Muster durch einen bestimmten Wert zu ersetzen.
Gedanken?
Antworten:
Haftungsausschluss: Dieser Ansatz ist eine naive Darstellung der Funktionen von Ruby und keine produktionsfähige Lösung zum Ersetzen von Zeichenfolgen in Dateien. Es ist anfällig für verschiedene Fehlerszenarien, z. B. Datenverlust im Falle eines Absturzes, einer Unterbrechung oder einer vollen Festplatte. Dieser Code eignet sich nur für ein schnelles einmaliges Skript, in dem alle Daten gesichert werden. Kopieren Sie diesen Code aus diesem Grund NICHT in Ihre Programme.
Hier ist ein kurzer kurzer Weg, um es zu tun.
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")
Tatsächlich verfügt Ruby über eine direkte Bearbeitungsfunktion. Wie Perl kann man sagen
ruby -pi.bak -e "gsub(/oldtext/, 'newtext')" *.txt
Dadurch wird der Code in doppelten Anführungszeichen auf alle Dateien im aktuellen Verzeichnis angewendet, deren Namen mit ".txt" enden. Sicherungskopien der bearbeiteten Dateien werden mit der Erweiterung ".bak" erstellt ("foobar.txt.bak", glaube ich).
HINWEIS: Dies scheint bei mehrzeiligen Suchen nicht zu funktionieren. Für diese muss man es anders machen, mit einem Wrapper-Skript um den regulären Ausdruck.
<main>': undefined method gsub 'für main: Object (NoMethodError)
-ian Ort und Stelle bearbeitet. .bakist die Erweiterung, die für eine Sicherungsdatei verwendet wird (optional). -pist so etwas wie while gets; <script>; puts $_; end. ( $_ist die letzte gelesene Zeile, aber Sie können sie für etwas wie zuweisen echo aa | ruby -p -e '$_.upcase!'.)
Beachten Sie, dass das Dateisystem in diesem Fall möglicherweise nicht genügend Speicherplatz hat und Sie möglicherweise eine Datei mit der Länge Null erstellen. Dies ist katastrophal, wenn Sie im Rahmen des Systemkonfigurationsmanagements beispielsweise / etc / passwd-Dateien schreiben.
Beachten Sie, dass die direkte Dateibearbeitung wie in der akzeptierten Antwort die Datei immer abschneidet und die neue Datei nacheinander ausschreibt. Es wird immer eine Race-Bedingung geben, bei der gleichzeitige Leser eine abgeschnittene Datei sehen. Wenn der Prozess während des Schreibvorgangs aus irgendeinem Grund abgebrochen wird (Strg-C, OOM-Killer, Systemabsturz, Stromausfall usw.), bleibt auch die abgeschnittene Datei übrig, was katastrophal sein kann. Dies ist die Art von Datenverlust-Szenario, die Entwickler berücksichtigen müssen, da dies passieren wird. Aus diesem Grund denke ich, dass die akzeptierte Antwort höchstwahrscheinlich nicht die akzeptierte Antwort sein sollte. Schreiben Sie mindestens in ein Tempfile und verschieben / benennen Sie die Datei wie die "einfache" Lösung am Ende dieser Antwort.
Sie müssen einen Algorithmus verwenden, der:
Liest die alte Datei und schreibt in die neue Datei. (Sie müssen vorsichtig sein, wenn Sie ganze Dateien in den Speicher schlürfen).
Schließt die neue temporäre Datei explizit. Hier können Sie eine Ausnahme auslösen, da die Dateipuffer nicht auf die Festplatte geschrieben werden können, da kein Speicherplatz vorhanden ist. (Fangen Sie dies ab und bereinigen Sie die temporäre Datei, wenn Sie möchten, aber Sie müssen etwas neu werfen oder an dieser Stelle ziemlich hart scheitern.
Behebt die Dateiberechtigungen und -modi für die neue Datei.
Benennt die neue Datei um und legt sie ab.
Mit ext3-Dateisystemen wird garantiert, dass das Schreiben von Metadaten zum Verschieben der Datei nicht vom Dateisystem neu angeordnet und geschrieben wird, bevor die Datenpuffer für die neue Datei geschrieben werden. Dies sollte also entweder erfolgreich sein oder fehlschlagen. Das ext4-Dateisystem wurde ebenfalls gepatcht, um diese Art von Verhalten zu unterstützen. Wenn Sie sehr paranoid sind, sollten Sie den fdatasync()Systemaufruf als Schritt 3.5 aufrufen, bevor Sie die Datei verschieben.
Unabhängig von der Sprache ist dies eine bewährte Methode. In Sprachen, in denen der Aufruf close()keine Ausnahme auslöst (Perl oder C), müssen Sie die Rückgabe von explizit überprüfen close()und eine Ausnahme auslösen, wenn dies fehlschlägt.
Mit dem obigen Vorschlag, die Datei einfach in den Speicher zu schlürfen, zu bearbeiten und in die Datei zu schreiben, werden garantiert Dateien mit der Länge Null auf einem vollständigen Dateisystem erstellt. Sie müssen immer verwenden FileUtils.mv, um eine vollständig geschriebene temporäre Datei an ihren Platz zu verschieben.
Eine letzte Überlegung ist die Platzierung der temporären Datei. Wenn Sie eine Datei in / tmp öffnen, müssen Sie einige Probleme berücksichtigen:
Wenn / tmp auf einem anderen Dateisystem bereitgestellt ist, wird / tmp möglicherweise nicht genügend Speicherplatz haben, bevor Sie die Datei geschrieben haben, die andernfalls am Ziel der alten Datei bereitgestellt werden könnte.
Wahrscheinlich noch wichtiger: Wenn Sie versuchen, mvdie Datei über einen Geräte-Mount zu übertragen, werden Sie transparent in cpVerhalten konvertiert . Die alte Datei wird geöffnet, der Inode der alten Dateien bleibt erhalten und wird erneut geöffnet, und der Dateiinhalt wird kopiert. Dies ist höchstwahrscheinlich nicht das, was Sie möchten, und es können Fehler "Textdatei beschäftigt" auftreten, wenn Sie versuchen, den Inhalt einer laufenden Datei zu bearbeiten. Dies macht auch den Zweck der Verwendung der Dateisystembefehle mvzunichte und Sie können das Zieldateisystem mit nur einer teilweise geschriebenen Datei aus dem Speicherplatz ausführen.
Dies hat auch nichts mit Rubys Implementierung zu tun. Das System mvund die cpBefehle verhalten sich ähnlich.
Was besser ist, ist das Öffnen einer Tempfile im selben Verzeichnis wie die alte Datei. Dies stellt sicher, dass keine geräteübergreifenden Verschiebungsprobleme auftreten. Das mvselbst sollte niemals fehlschlagen, und Sie sollten immer eine vollständige und nicht abgeschnittene Datei erhalten. Alle Fehler, wie z. B. nicht genügend Speicherplatz auf dem Gerät, Berechtigungsfehler usw., sollten beim Ausschreiben der Tempfile auftreten.
Die einzigen Nachteile beim Erstellen der Tempfile im Zielverzeichnis sind:
Hier ist ein Code, der den vollständigen Algorithmus implementiert (Windows-Code ist ungetestet und unvollendet):
#!/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")
Und hier ist eine etwas engere Version, die sich nicht um jeden möglichen Randfall kümmert (wenn Sie unter Unix arbeiten und sich nicht für das Schreiben an / proc interessieren):
#!/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")
Der wirklich einfache Anwendungsfall, wenn Sie sich nicht für Dateisystemberechtigungen interessieren (entweder werden Sie nicht als root ausgeführt oder Sie werden als root ausgeführt und die Datei befindet sich im Root-Besitz):
#!/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 : Dies sollte in jedem Fall mindestens anstelle der akzeptierten Antwort verwendet werden, um sicherzustellen, dass das Update atomar ist und gleichzeitige Leser keine abgeschnittenen Dateien sehen. Wie oben erwähnt, ist es hier wichtig, das Tempfile im selben Verzeichnis wie die bearbeitete Datei zu erstellen, um zu vermeiden, dass geräteübergreifende MV-Vorgänge in CP-Vorgänge übersetzt werden, wenn / tmp auf einem anderen Gerät bereitgestellt wird. Das Aufrufen von fdatasync ist eine zusätzliche Ebene der Paranoia, führt jedoch zu einem Leistungseinbruch. Daher habe ich es in diesem Beispiel weggelassen, da es nicht häufig praktiziert wird.
Es gibt nicht wirklich eine Möglichkeit, Dateien direkt zu bearbeiten. Was Sie normalerweise tun, wenn Sie damit durchkommen können (dh wenn die Dateien nicht zu groß sind), ist, dass Sie die Datei in den Speicher lesen ( File.read), Ihre Ersetzungen an der gelesenen Zeichenfolge ( String#gsub) vornehmen und dann die geänderte Zeichenfolge zurück in die Datei schreiben Datei ( File.open, File#write).
Wenn die Dateien groß genug dafür zu sein , nicht machbar sind, was Sie tun müssen, ist die Datei in Blöcken gelesen (wenn das Muster , das Sie ersetzen möchten nicht über mehrere Zeilen erstrecken dann einen Chunk bedeutet in der Regel eine Zeile - können Sie verwenden , File.foreachum Lesen Sie eine Datei Zeile für Zeile), und führen Sie für jeden Block die Ersetzung durch und hängen Sie sie an eine temporäre Datei an. Wenn Sie die Quelldatei durchlaufen haben, schließen Sie sie und FileUtils.mvüberschreiben sie mit der temporären Datei.
Ein anderer Ansatz ist die Verwendung der Inplace-Bearbeitung in Ruby (nicht über die Befehlszeile):
#!/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
Wenn Sie kein Backup erstellen möchten, wechseln Sie '.bak'zu ''.
readdie Datei zu schlürfen ( ). Es ist skalierbar und sollte sehr schnell sein.
Das funktioniert bei mir:
filename = "foo"
text = File.read(filename)
content = text.gsub(/search_regexp/, "replacestring")
File.open(filename, "w") { |file| file << content }
Hier ist eine Lösung zum Suchen / Ersetzen in allen Dateien eines bestimmten Verzeichnisses. Grundsätzlich habe ich die Antwort von sepp2k genommen und erweitert.
# 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) }
Wenn Sie Ersetzungen über Zeilengrenzen hinweg durchführen müssen, ruby -pi -efunktioniert die Verwendung nicht, da die pVerarbeitung zeilenweise erfolgt. Stattdessen empfehle ich Folgendes, obwohl es mit einer Datei mit mehreren GB fehlschlagen könnte:
ruby -e "file='translation.ja.yml'; IO.write(file, (IO.read(file).gsub(/\s+'$/, %q('))))"
Das sucht nach Leerzeichen (möglicherweise einschließlich neuer Zeilen), gefolgt von einem Anführungszeichen. In diesem Fall wird das Leerzeichen entfernt. Das %q(')ist nur eine andere Art , die Anführungszeichen zu zitieren.
Hier eine Alternative zum One Liner von Jim, diesmal in einem Skript
ARGV[0..-3].each{|f| File.write(f, File.read(f).gsub(ARGV[-2],ARGV[-1]))}
Speichern Sie es in einem Skript, z. B. replace.rb
Sie beginnen in der Befehlszeile mit
replace.rb *.txt <string_to_replace> <replacement>
* .txt kann durch eine andere Auswahl oder durch einige Dateinamen oder Pfade ersetzt werden
Aufgeschlüsselt, damit ich erklären kann, was passiert, aber immer noch ausführbar ist
# 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
BEARBEITEN: Wenn Sie einen regulären Ausdruck verwenden möchten, verwenden Sie diesen stattdessen. Dies gilt natürlich nur für den Umgang mit relativ kleinen Textdateien, keine Gigabyte-Monster
ARGV[0..-3].each{|f| File.write(f, File.read(f).gsub(/#{ARGV[-2]}/,ARGV[-1]))}
File.readmit den Informationen in stackoverflow.com/a/25189286/128421 gemildert werden müssen, warum das Schlürfen großer Dateien schlecht ist. Auch anstelle vonFile.open(filename, "w") { |file| file << content }Variationen verwendenFile.write(filename, content).