В ответах ниже имейте в виду, что любые рекомендации по использованию File.readдолжны быть дополнены информацией из stackoverflow.com/a/25189286/128421 о том, почему прихлебывать большие файлы - это плохо. Также вместо File.open(filename, "w") { |file| file << content }вариаций используйте File.write(filename, content).
Железный Человек
Ответы:
190
Отказ от ответственности: этот подход является наивной иллюстрацией возможностей 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
Записывает ли put изменения обратно в файл? Я думал, что это просто напечатает контент на консоли.
Дэйн О'Коннор,
Да, он выводит содержимое на консоль.
sepp2k
7
Да, я не был уверен, что ты этого хочешь. Для записи используйте File.open (имя_файла, "w") {| file | file.puts output_of_gsub}
Макс Черняк
7
Мне пришлось использовать file.write: File.open (имя_файла, "w") {| file | file.write (text)}
Остин
3
Чтобы записать файл, замените строку File.write(file_name, text.gsub(/regexp/, "replace")
tight
106
На самом деле в Ruby есть функция редактирования на месте. Как и Perl, вы можете сказать
ruby -pi.bak -e "gsub(/oldtext/, 'newtext')"*.txt
Это применит код в двойных кавычках ко всем файлам в текущем каталоге, имена которых заканчиваются на «.txt». Резервные копии редактируемых файлов будут создаваться с расширением «.bak» (думаю, «foobar.txt.bak»).
ПРИМЕЧАНИЕ: похоже, это не работает для многострочного поиска. Для них вы должны сделать это другим, менее красивым способом, с помощью сценария-оболочки вокруг регулярного выражения.
Что, черт возьми, такое пи.бак? Без этого я получаю ошибку. -e: 1: in <main>': undefined method gsub 'для main: Object (NoMethodError)
Ninad
15
@NinadPachpute -iправки на месте. .bak- расширение, используемое для файла резервной копии (необязательно). -pэто что-то вроде while gets; <script>; puts $_; end. ( $_это последняя прочитанная строка, но вы можете назначить ей что-то вроде echo aa | ruby -p -e '$_.upcase!'.)
Пт,
1
Это лучший ответ, чем принятый ответ, IMHO, если вы хотите изменить файл.
Colin K
6
Как я могу использовать это внутри скрипта ruby ??
Саураб
1
Есть много причин, по которым это может пойти не так, поэтому тщательно протестируйте его, прежде чем пытаться применить его к важному файлу.
Железный Человек
49
Имейте в виду, что при этом в файловой системе может не хватить места, и вы можете создать файл нулевой длины. Это катастрофа, если вы делаете что-то вроде записи файлов / etc / passwd в рамках управления конфигурацией системы.
Обратите внимание, что редактирование файла на месте, как в принятом ответе, всегда будет усекать файл и последовательно записывать новый файл. Всегда будет состояние гонки, при котором одновременные читатели увидят усеченный файл. Если процесс прерывается по какой-либо причине (ctrl-c, убийца OOM, сбой системы, отключение питания и т. Д.) Во время записи, то усеченный файл также останется, что может иметь катастрофические последствия. Это тот сценарий потери данных, который разработчики ДОЛЖНЫ учитывать, потому что это произойдет. По этой причине я думаю, что принятый ответ, скорее всего, не должен быть принятым ответом. Как минимум напишите во временный файл и переместите / переименуйте файл на место, как «простое» решение в конце этого ответа.
Вам необходимо использовать алгоритм, который:
Читает старый файл и записывает в новый файл. (Вам нужно быть осторожным, чтобы целые файлы не попали в память).
Явно закрывает новый временный файл, в котором вы можете вызвать исключение, потому что файловые буферы не могут быть записаны на диск из-за отсутствия места. (Поймайте это и очистите временный файл, если хотите, но на этом этапе вам нужно что-то перебросить заново или довольно сильно выйти из строя.
Исправляет права доступа и режимы для нового файла.
Переименовывает новый файл и вставляет его на место.
С файловыми системами ext3 вам гарантируется, что метаданные, записываемые для перемещения файла на место, не будут переупорядочены файловой системой и записаны до того, как будут записаны буферы данных для нового файла, поэтому это должно быть либо успешно, либо неуспешно. Файловая система ext4 также была исправлена для поддержки такого поведения. Если вы очень параноик, вам следует вызвать fdatasync()системный вызов в качестве шага 3.5 перед перемещением файла на место.
Независимо от языка это лучшая практика. В языках, где вызов close()не вызывает исключения (Perl или C), вы должны явно проверить возврат close()и выбросить исключение в случае сбоя.
Приведенное выше предложение просто поместить файл в память, манипулировать им и записать его в файл гарантированно приведет к созданию файлов нулевой длины в полной файловой системе. Вам необходимо всегда использовать FileUtils.mvдля перемещения полностью записанного временного файла на место.
Последнее соображение - это размещение временного файла. Если вы открываете файл в / tmp, вы должны учитывать несколько проблем:
Если / tmp смонтирован в другой файловой системе, вы можете запустить / tmp из-за отсутствия свободного места, прежде чем записать файл, который в противном случае можно было бы развернуть в место назначения старого файла.
Вероятно, что еще более важно, когда вы пытаетесь подключить mvфайл через устройство, вы прозрачно конвертируетесь в cpповедение. Старый файл будет открыт, индексный дескриптор старых файлов будет сохранен и повторно открыт, а содержимое файла будет скопировано. Скорее всего, это не то, что вам нужно, и вы можете столкнуться с ошибкой «текстовый файл занят», если попытаетесь отредактировать содержимое работающего файла. Это также противоречит цели использования mvкоманд файловой системы, и вы можете запустить целевую файловую систему из-за недостатка места только с частично записанным файлом.
Это также не имеет ничего общего с реализацией Ruby. Система mvи cpкоманды ведут себя аналогично.
Более предпочтительно открыть временный файл в том же каталоге, что и старый файл. Это гарантирует, что не возникнет проблем с перемещением между устройствами. Сам по mvсебе никогда не должен выходить из строя, и вы всегда должны получать полный и не усеченный файл. Любые сбои, такие как нехватка места на устройстве, ошибки разрешений и т. Д., Должны возникать во время записи временного файла.
Единственными недостатками подхода к созданию временного файла в целевом каталоге являются:
Иногда вы не сможете открыть там временный файл, например, если вы пытаетесь «отредактировать» файл в / proc. По этой причине вы можете вернуться и попробовать / tmp, если открытие файла в целевом каталоге не удается.
У вас должно быть достаточно места в целевом разделе, чтобы вместить как старый, так и новый файл полностью. Однако, если у вас недостаточно места для хранения обеих копий, тогда у вас, вероятно, не хватает места на диске, и фактический риск записи усеченного файла намного выше, поэтому я бы сказал, что это очень плохой компромисс за пределами некоторых чрезвычайно узких (и хорошо контролируемые) крайние случаи.
Вот код, реализующий полный алгоритм (код Windows непроверенный и незаконченный):
А вот немного более плотная версия, которая не заботится обо всех возможных крайних случаях (если вы используете Unix и не заботитесь о записи в / proc):
Действительно простой вариант использования, когда вам не важны разрешения файловой системы (либо вы работаете не как root, либо вы работаете как root, а файл принадлежит root):
TL; DR : как минимум, это следует использовать вместо принятого ответа во всех случаях, чтобы гарантировать, что обновление является атомарным и одновременные читатели не увидят усеченные файлы. Как я упоминал выше, создание временного файла в том же каталоге, что и отредактированный файл, важно здесь, чтобы избежать преобразования mv-операций между устройствами в операции cp, если / tmp смонтирован на другом устройстве. Вызов fdatasync - это дополнительный уровень паранойи, но он приведет к снижению производительности, поэтому я пропустил его в этом примере, поскольку он обычно не практикуется.
Вместо того, чтобы открывать временный файл в каталоге, в котором вы находитесь, он фактически автоматически создает его в каталоге данных приложения (в любом случае в Windows), и из них вы можете удалить файл file.unlink, чтобы удалить его ..
13aal
3
Я действительно оценил дополнительные мысли, которые были вложены в это. Новичку очень интересно наблюдать за образцами мышления опытных разработчиков, которые могут не только ответить на исходный вопрос, но и прокомментировать более широкий контекст того, что на самом деле означает исходный вопрос.
ramijames 02
Программирование - это не только решение сиюминутной проблемы, но и умение думать на будущее, чтобы избежать других проблем, подстерегающих. Ничто так не раздражает старшего разработчика, как столкновение с кодом, который загнал алгоритм в угол, создавая неудобную путаницу, когда небольшая корректировка ранее привела бы к хорошему потоку. Часто для понимания цели могут потребоваться часы или дни, а затем несколько строк заменяют страницу старого кода. Иногда это похоже на игру в шахматы с данными и системой.
Железный Человек
11
На самом деле нет способа редактировать файлы на месте. Что вы обычно делаете, когда это может сойти с рук (например, если файлы не слишком большие), вы читаете файл в memory ( File.read), выполняете свои замены в строке чтения ( String#gsub), а затем записываете измененную строку обратно в файл ( File.open, File#write).
Если файлы достаточно велики, чтобы это было невозможно, вам нужно прочитать файл по частям (если шаблон, который вы хотите заменить, не будет охватывать несколько строк, то один фрагмент обычно означает одну строку - вы можете использовать File.foreachдля читать файл построчно), и для каждого фрагмента выполнить замену в нем и добавить его во временный файл. Когда вы закончите перебирать исходный файл, вы закрываете его и используете FileUtils.mvдля перезаписи временным файлом.
Другой подход - использовать редактирование на месте внутри Ruby (не из командной строки):
#!/usr/bin/rubydef 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'на ''.
Это было бы лучше, чем пытаться использовать slurp ( read) для файла. Он масштабируемый и должен быть очень быстрым.
The Tin Man
Где-то есть ошибка, приводящая к сбою Ruby 2.3.0p0 в Windows с отказом в разрешении, если с одним файлом работают несколько последовательных блоков inplace_edit. Воспроизвести разбиение тестов search1 и search2 на 2 блока. Не закрывается полностью?
mlt
Я бы ожидал, что проблемы с одновременным редактированием нескольких текстовых файлов будут. Если уж на то пошло, вы можете получить сильно искаженный текстовый файл.
Вот решение для поиска / замены во всех файлах данного каталога. В основном я взял ответ, предоставленный 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
Будет лучше, если вы объясните, почему это предпочтительное решение, и объясните, как оно работает. Мы хотим обучать, а не просто предоставлять код.
Железный Человек
trollop был переименован в optimist github.com/manageiq/optimist . Кроме того, это просто синтаксический анализатор параметров интерфейса командной строки, который на самом деле не требуется для ответа на вопрос.
noraj
1
Если вам нужно выполнить замену через границы строк, то использование ruby -pi -eне будет работать, потому что pобрабатывается одна строка за раз. Вместо этого я рекомендую следующее, хотя это может привести к сбою с файлом размером в несколько ГБ:
Ищет пробелы (потенциально включая новые строки), за которыми следует кавычка, и в этом случае он избавляется от пробелов. Это %q(')просто причудливый способ цитирования символа кавычки.
* .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 2File.write(f,# open the argument (= filename) for writingFile.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
Этот код работать не будет. Я предлагаю протестировать его перед публикацией, а затем скопировать и вставить рабочий код.
Железный Человек
@theTinMan Я всегда тестирую перед публикацией, если это возможно. Я проверил это, и он работает, как в версии с комментариями. Как вы думаете, почему нет?
питер
если вы имеете в виду , используя регулярное выражение , видеть мое редактирование, также протестировали:>)
File.read
должны быть дополнены информацией из stackoverflow.com/a/25189286/128421 о том, почему прихлебывать большие файлы - это плохо. Также вместоFile.open(filename, "w") { |file| file << content }
вариаций используйтеFile.write(filename, content)
.Ответы:
Отказ от ответственности: этот подход является наивной иллюстрацией возможностей Ruby, а не производственным решением для замены строк в файлах. Он подвержен различным сценариям сбоев, таким как потеря данных в случае сбоя, прерывания или переполнения диска. Этот код не годится ни для чего, кроме быстрого одноразового скрипта, в котором создается резервная копия всех данных. По этой причине НЕ копируйте этот код в свои программы.
Вот быстрый способ сделать это.
источник
File.write(file_name, text.gsub(/regexp/, "replace")
На самом деле в Ruby есть функция редактирования на месте. Как и Perl, вы можете сказать
Это применит код в двойных кавычках ко всем файлам в текущем каталоге, имена которых заканчиваются на «.txt». Резервные копии редактируемых файлов будут создаваться с расширением «.bak» (думаю, «foobar.txt.bak»).
ПРИМЕЧАНИЕ: похоже, это не работает для многострочного поиска. Для них вы должны сделать это другим, менее красивым способом, с помощью сценария-оболочки вокруг регулярного выражения.
источник
<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, сбой системы, отключение питания и т. Д.) Во время записи, то усеченный файл также останется, что может иметь катастрофические последствия. Это тот сценарий потери данных, который разработчики ДОЛЖНЫ учитывать, потому что это произойдет. По этой причине я думаю, что принятый ответ, скорее всего, не должен быть принятым ответом. Как минимум напишите во временный файл и переместите / переименуйте файл на место, как «простое» решение в конце этого ответа.
Вам необходимо использовать алгоритм, который:
Читает старый файл и записывает в новый файл. (Вам нужно быть осторожным, чтобы целые файлы не попали в память).
Явно закрывает новый временный файл, в котором вы можете вызвать исключение, потому что файловые буферы не могут быть записаны на диск из-за отсутствия места. (Поймайте это и очистите временный файл, если хотите, но на этом этапе вам нужно что-то перебросить заново или довольно сильно выйти из строя.
Исправляет права доступа и режимы для нового файла.
Переименовывает новый файл и вставляет его на место.
С файловыми системами ext3 вам гарантируется, что метаданные, записываемые для перемещения файла на место, не будут переупорядочены файловой системой и записаны до того, как будут записаны буферы данных для нового файла, поэтому это должно быть либо успешно, либо неуспешно. Файловая система ext4 также была исправлена для поддержки такого поведения. Если вы очень параноик, вам следует вызвать
fdatasync()
системный вызов в качестве шага 3.5 перед перемещением файла на место.Независимо от языка это лучшая практика. В языках, где вызов
close()
не вызывает исключения (Perl или C), вы должны явно проверить возвратclose()
и выбросить исключение в случае сбоя.Приведенное выше предложение просто поместить файл в память, манипулировать им и записать его в файл гарантированно приведет к созданию файлов нулевой длины в полной файловой системе. Вам необходимо всегда использовать
FileUtils.mv
для перемещения полностью записанного временного файла на место.Последнее соображение - это размещение временного файла. Если вы открываете файл в / tmp, вы должны учитывать несколько проблем:
Если / tmp смонтирован в другой файловой системе, вы можете запустить / tmp из-за отсутствия свободного места, прежде чем записать файл, который в противном случае можно было бы развернуть в место назначения старого файла.
Вероятно, что еще более важно, когда вы пытаетесь подключить
mv
файл через устройство, вы прозрачно конвертируетесь вcp
поведение. Старый файл будет открыт, индексный дескриптор старых файлов будет сохранен и повторно открыт, а содержимое файла будет скопировано. Скорее всего, это не то, что вам нужно, и вы можете столкнуться с ошибкой «текстовый файл занят», если попытаетесь отредактировать содержимое работающего файла. Это также противоречит цели использованияmv
команд файловой системы, и вы можете запустить целевую файловую систему из-за недостатка места только с частично записанным файлом.Это также не имеет ничего общего с реализацией Ruby. Система
mv
иcp
команды ведут себя аналогично.Более предпочтительно открыть временный файл в том же каталоге, что и старый файл. Это гарантирует, что не возникнет проблем с перемещением между устройствами. Сам по
mv
себе никогда не должен выходить из строя, и вы всегда должны получать полный и не усеченный файл. Любые сбои, такие как нехватка места на устройстве, ошибки разрешений и т. Д., Должны возникать во время записи временного файла.Единственными недостатками подхода к созданию временного файла в целевом каталоге являются:
Вот код, реализующий полный алгоритм (код Windows непроверенный и незаконченный):
А вот немного более плотная версия, которая не заботится обо всех возможных крайних случаях (если вы используете Unix и не заботитесь о записи в / proc):
Действительно простой вариант использования, когда вам не важны разрешения файловой системы (либо вы работаете не как root, либо вы работаете как root, а файл принадлежит root):
TL; DR : как минимум, это следует использовать вместо принятого ответа во всех случаях, чтобы гарантировать, что обновление является атомарным и одновременные читатели не увидят усеченные файлы. Как я упоминал выше, создание временного файла в том же каталоге, что и отредактированный файл, важно здесь, чтобы избежать преобразования mv-операций между устройствами в операции cp, если / tmp смонтирован на другом устройстве. Вызов fdatasync - это дополнительный уровень паранойи, но он приведет к снижению производительности, поэтому я пропустил его в этом примере, поскольку он обычно не практикуется.
источник
На самом деле нет способа редактировать файлы на месте. Что вы обычно делаете, когда это может сойти с рук (например, если файлы не слишком большие), вы читаете файл в memory (
File.read
), выполняете свои замены в строке чтения (String#gsub
), а затем записываете измененную строку обратно в файл (File.open
,File#write
).Если файлы достаточно велики, чтобы это было невозможно, вам нужно прочитать файл по частям (если шаблон, который вы хотите заменить, не будет охватывать несколько строк, то один фрагмент обычно означает одну строку - вы можете использовать
File.foreach
для читать файл построчно), и для каждого фрагмента выполнить замену в нем и добавить его во временный файл. Когда вы закончите перебирать исходный файл, вы закрываете его и используетеFileUtils.mv
для перезаписи временным файлом.источник
Другой подход - использовать редактирование на месте внутри Ruby (не из командной строки):
Если вы не хотите создавать резервную копию, измените
'.bak'
на''
.источник
read
) для файла. Он масштабируемый и должен быть очень быстрым.Это работает для меня:
источник
Вот решение для поиска / замены во всех файлах данного каталога. В основном я взял ответ, предоставленный sepp2k, и расширил его.
источник
источник
Если вам нужно выполнить замену через границы строк, то использование
ruby -pi -e
не будет работать, потому чтоp
обрабатывается одна строка за раз. Вместо этого я рекомендую следующее, хотя это может привести к сбою с файлом размером в несколько ГБ:Ищет пробелы (потенциально включая новые строки), за которыми следует кавычка, и в этом случае он избавляется от пробелов. Это
%q(')
просто причудливый способ цитирования символа кавычки.источник
Вот альтернатива одному вкладышу от Джима, на этот раз в скрипте
Сохраните его в скрипте, например replace.rb
Вы начинаете в командной строке с
* .txt можно заменить другим выбором или некоторыми именами файлов или путями
разбит, чтобы я мог объяснить, что происходит, но все еще исполняемый
РЕДАКТИРОВАТЬ: если вы хотите использовать регулярное выражение, используйте это вместо этого Очевидно, это только для обработки относительно небольших текстовых файлов, без монстров Gigabyte
источник