programing

파일 텍스트에서 패턴을 검색하고 지정된 값으로 바꾸는 방법

closeapi 2023. 6. 10. 09:07
반응형

파일 텍스트에서 패턴을 검색하고 지정된 값으로 바꾸는 방법

파일(또는 파일 목록)에서 패턴을 검색하고 찾을 경우 해당 패턴을 지정된 값으로 바꿀 스크립트를 찾고 있습니다.

생각은?

고지 사항: 이 접근 방식은 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

사실, Ruby는 내부 편집 기능이 있습니다.Perl처럼, 당신은 말할 수 있습니다.

ruby -pi.bak -e "gsub(/oldtext/, 'newtext')" *.txt

이렇게 하면 이름이 ".txt"로 끝나는 현재 디렉터리의 모든 파일에 이중 따옴표 코드가 적용됩니다.편집된 파일의 백업 복사본은 ".bak" 확장자("foobar")로 생성됩니다.txt.bak"라고 생각합니다.

참고: 여러 줄 검색에는 사용할 수 없는 것 같습니다.그런 경우에는 정규식 주위에 래퍼 스크립트를 사용하여 덜 예쁜 방법으로 해야 합니다.

이렇게 하면 파일 시스템의 공간이 부족하여 길이가 0인 파일을 만들 수 있습니다.시스템 구성 관리의 일부로 /etc/passwd 파일을 작성하는 것과 같은 작업을 수행하는 경우 이는 치명적입니다.

승인된 답변과 같은 내부 파일 편집은 항상 파일을 잘라내고 새 파일을 순차적으로 씁니다.동시 판독기에 잘린 파일이 표시되는 경합 조건은 항상 존재합니다.쓰기 도중 어떤 이유(ctrl-c, OOM killer, 시스템 충돌, 정전 등)로 인해 프로세스가 중단되면 잘린 파일도 남아 치명적일 수 있습니다.이것은 데이터 손실 시나리오의 일종으로 개발자들은 이와 같은 상황이 발생할 것이기 때문에 반드시 고려해야 합니다.그렇기 때문에 저는 인정된 답이 인정된 답이 되어서는 안 된다고 생각합니다.최소한 임시 파일에 쓰고 이 답변의 끝에 있는 "단순한" 솔루션처럼 파일을 제자리로 이동/이름 변경합니다.

다음과 같은 알고리즘을 사용해야 합니다.

  1. 이전 파일을 읽고 새 파일에 씁니다. (전체 파일을 메모리에 슬러핑하는 데 주의해야 합니다.)

  2. 새 임시 파일을 명시적으로 닫습니다. 이 파일 버퍼는 공간이 없기 때문에 디스크에 쓸 수 없기 때문에 예외를 발생시킬 수 있습니다. (이 파일을 캡처하여 원하는 경우 임시 파일을 정리하십시오. 하지만 이 시점에서 무언가를 다시 던지거나 상당히 심하게 실패해야 합니다.)

  3. 새 파일에 대한 파일 권한 및 모드를 수정합니다.

  4. 새 파일의 이름을 바꾸고 파일을 제자리에 놓습니다.

ext3 파일 시스템에서는 파일을 제자리로 이동하기 위한 메타데이터 쓰기가 파일 시스템에 의해 재배열되어 새 파일의 데이터 버퍼가 쓰기 전에 기록되지 않으므로 성공하거나 실패할 수 있습니다. 패치가 되었습니다. ext4 파일 시스템은 다음과 .만약 당신이 매우 편집증적이라면, 당신은 전화해야 합니다.fdatasync()파일을 제자리로 이동하기 전에 3.5단계로 시스템 호출을 수행합니다.

언어에 관계없이 이 방법이 최선의 방법입니다.전화를 거는 언어에서는close()C). 예를던지않음지의(Perl 또는 C)는환반의 해야 합니다.close()실패할 경우 예외를 설정합니다.

위의 제안은 단순히 파일을 메모리로 후루룩 집어넣고, 조작하여 파일에 기록하는 것으로 전체 파일 시스템에서 길이가 0인 파일을 생성할 수 있습니다.항상 다음을 사용해야 합니다.FileUtils.mv완전히 작성된 임시 파일을 제자리로 이동합니다.

마지막 고려 사항은 임시 파일의 배치입니다./tmp에서 파일을 여는 경우 다음과 같은 몇 가지 문제를 고려해야 합니다.

  • /tmp가 다른 파일 시스템에 마운트된 경우 이전 파일의 대상에 배포할 수 있는 파일을 쓰기 전에 /tmp의 공간이 부족해질 수 있습니다.

  • 아마도 더 중요한 것은, 당신이 노력할 때.mv은 투하게변장치마로 변환됩니다.cp 파일 , 됩니다.이전 파일이 열리고, 이전 파일 inode가 보존되었다가 다시 열리며, 파일 내용이 복사됩니다.이것은 대부분 사용자가 원하는 것이 아니며, 실행 중인 파일의 내용을 편집하려고 하면 "텍스트 파일 사용 중" 오류가 발생할 수 있습니다.은 또한 시스템을 합니다.mv명령을 실행하면 부분적으로 작성된 파일로만 대상 파일 시스템을 공간 부족으로 실행할 수 있습니다.

    이는 루비의 구현과도 무관합니다.mv그리고.cp명령은 유사하게 작동합니다.

이전 파일과 동일한 디렉토리에서 Temp 파일을 여는 것이 더 좋습니다.이렇게 하면 장치 간 이동 문제가 발생하지 않습니다.mv오류가 발생해서는 안 되며 항상 완전하고 잘리지 않은 파일을 얻어야 합니다.Temp 파일을 쓰는 동안 장치 공간 부족, 권한 오류 등과 같은 오류가 발생해야 합니다.

대상 디렉터리에 Temp 파일을 만드는 방법의 유일한 단점은 다음과 같습니다.

  • 예를 들어 /proc의 파일을 '편집'하려는 경우와 같이 임시 파일을 열 수 없는 경우가 있습니다.따라서 대상 디렉터리에서 파일을 열지 못하면 뒤로 물러서서 /tmp를 시도할 수 있습니다.
  • 전체 이전 파일과 새 파일을 모두 저장하려면 대상 파티션에 충분한 공간이 있어야 합니다.그러나 두 복사본을 모두 저장할 공간이 부족한 경우에는 Disk 공간이 부족할 수 있으며 잘린 파일을 쓸 위험이 훨씬 높기 때문에 일부 매우 좁은(모니터링이 잘 된) 에지 사례를 제외하면 이는 매우 낮은 트레이드오프라고 생각합니다.

다음은 전체 알고리즘을 구현하는 코드입니다(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: 업데이트가 원자적이며 동시 판독기에 잘린 파일이 표시되지 않도록 하려면 모든 경우에 최소한 승인된 답변 대신 사용해야 합니다.위에서 언급했듯이 /tmp가 다른 장치에 마운트된 경우 교차 장치 mv 작업이 cp 작업으로 변환되지 않도록 편집된 파일과 동일한 디렉토리에 Temp 파일을 만드는 것이 여기서 중요합니다.fdatasync를 호출하는 것은 편집증의 추가 레이어이지만 성능 타격을 초래할 것이기 때문에 일반적으로 실행되지 않기 때문에 이 예에서 생략했습니다.

파일을 편집할 수 있는 방법이 없습니다.파일 크기가 너무 크지 않은 경우 파일을 메모리에 읽어 들이는 것이 일반적입니다.File.read), ()에 작업을 String#gsub 후에 기록합니다.File.open,File#write).

을 읽는 줄에 있지줄을 합니다). 이실 불파정교읽것크다니입는파청을일면크로충도행가할줄걸한줄있의일에않쳐하청지을으면다니미로으합적반크나는여러이패분능로턴체할히교▁if▁-▁use▁you▁you한▁can▁(▁the줄,if▁that▁are의▁need일있않▁line▁in청하▁chunks▁enough▁what▁do▁one을걸으▁file▁read▁files면▁to,▁big▁youFile.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'''.

이것은 나에게 도움이 됩니다.

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) }

를 넘어 할에는 선경를넘대작체업수을행야해하는다사경다니용합음을우어계▁if▁bound다▁then사니▁across용합utions▁substit▁line,▁usingaries다을음▁you경우를 사용합니다.ruby -pi -e작동하지 않을 것입니다. 왜냐하면p한 번에 한 줄씩 처리합니다.대신 다중 GB 파일에서 실패할 수 있지만 다음을 권장합니다.

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)

다음 명령행에서 시작합니다.

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]))}

나는 tty-file gem을 사용하고 있습니다.

교체하는 것 외에도 추가, 추가(파일 내부의 지정된 텍스트/레거시), diff 등이 포함됩니다.

언급URL : https://stackoverflow.com/questions/1274605/how-to-search-file-text-for-a-pattern-and-replace-it-with-a-given-value

반응형