IT

패턴에 대한 파일 텍스트를 검색하고 주어진 값으로 바꾸는 방법

lottoking 2020. 7. 21. 07:38
반응형

패턴에 대한 파일 텍스트를 검색하고 주어진 값으로 바꾸는 방법


패턴을 찾기 위해 파일 (또는 파일 목록)을 검색하고 해당 패턴을 주어진 값으로 바꾸는 펼쳐 있습니다.

생각?


면책 조항 : 이은 루비의 기능에 대한 순진한 예이며 존재하는 것입니다. 충돌, 대규모 또는 디스크가 가득 찬 경우 데이터 성능과 같은 다양한 장애 시나리오가 발생하기 쉽습니다. 이 코드는 모든 데이터가 백업되는 빠른 일회성 외에는 적합하지 않습니다. 따라서이 코드를 프로그램에 복사하지 않습니다.

간단한 방법은 다음과 가변합니다.

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 킬러, 시스템 충돌, 정전 등) 잘린 파일도 다리 져져 있습니다. 이 개발자가 반드시해야 할 유의 데이터 데이터 시나리오입니다. 이런 M, 나는 수용된 대답이 대답이 아닐 수 있다고 생각합니다. 최소한 임시 파일에 쓰고이 답변의 끝에 "간단한"솔루션과 같은 위치로 파일을 이동 / 이름을 바꿉니다.]

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

  1. 이전 파일을 읽고 새 파일에 씁니다. (전체 파일을 메모리에 넣는 것에주의해야합니다).

  2. 공간이 있기 때문에 파일 버퍼를 디스크에 쓸 수 없기 때문에 예외가 보관 수있는 새 임시 파일을 명시 적으로 닫습니다. (원하는 경우를 잡아서 임시 파일을 정리하지만 시점에서 문제를 다시 던지거나 상당히 실패해야합니다.

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

  4. 새 파일의 이름을 바꾸고 제자리에 매.

ext3 파일 시스템을 사용하면 파일을 제자리로 패기위한 메타 데이터 쓰기가 파일 시스템에 의해 재 배열되지 않고 새 파일의 데이터 버퍼가 쓰기 전에 대학지지 성공 또는 실패해야합니다. ext4 파일 시스템도 종류의 동작을 지원합니다. 편집증이 매우 심하면 fdatasync()파일을 제자리로 시작하기 전에 3.5 단계로 시스템 호출을 호출 해야합니다 .

언어에 관계없이 가장 좋습니다. 호출 close()이 예외 (Perl 또는 C)가 발생하지 않는 언어에서는 예외의 리턴을 확인하고 close()예외가 발생하면 예외가 발생 합니다.

위의 제안은 파일을 메모리에 생성, 조작 파일에 쓰라는 것이 전체 파일 시스템에서 길이를 보장합니다. 작성된 임시 파일 완전히을 제자리로 옮기 려면 항상 사용해야 FileUtils.mv합니다.

마지막 고려 사항은 임시 파일의 배치입니다. / tmp에서 파일을 열면 몇 가지 문제를 정리합니다.

  • / tmp가 다른 파일 시스템에 마운트 된 경우 이전 파일의 대상에 배치 할 수있는 파일을 작성하기 전에 / tmp 공간이 부족할 수 있습니다.
  • 아마도 더 중요한 것은 mv마운트에서 파일을 시도 하면 투명하게 cp동작으로 변환 됩니다. 이전 파일이 열리고 이전 파일 inode가 유지되고 다시 열리고 파일 내용이 복사됩니다. "텍스트 파일 사용 중"오류가 보안 대상이 될 가능성이 있습니다. 여기서 또한 파일 시스템 mv명령 을 사용하는 목적을 상실하고 부분적으로 파일 시스템을 사용할 수 있습니다.

    루비의 구현과는 관련이 없습니다. 시스템 mvcp명령은 대화하게 동작합니다.

더 바람직한 것은 이전 파일과 동일한 디렉토리에서 임시 파일을 여는 것입니다. 이를 통해 장치 간 이동 문제가 발생하지 않습니다. mv자체는 절대 실패하지 안 파일을 항상 완전하고 잘리지 않습니다. 임시 파일을 쓰는 동안 장치 공간 부족, 권한 오류 발생 같은 모든 오류가 발생해야합니다.

대상 디렉토리에 Tempfile을 작성하는 방법에 대한 유일한 단점은 다음과 같습니다.

  • 예를 들어 / proc에서 파일을 '편집'하려는 경우와 같이 임시 파일을 열지 할 수도 있습니다. 대상 디렉토리에서 파일을 열 수없는 경우 폴백하고 / tmp를 시도 할 수 있습니다.
  • 이전 파일과 새 파일을 모두 보유하고있는 대상 파티션에 충분한 공간이 있어야합니다. 그러나 두 개의 사본을 모두 보유 할 공간이 충분하지 않은 파일을 사용하는 공간이 부족하고 높은 수준의 매우 좋지 않은 트레이드 오프라고 주장합니다. -모니터링 된 엣지 케이스.

다음은 전체 알고리즘을 구현하는 코드입니다 (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")

그리고 여기에 모든 가능한 대소 문자를 걱정하지 않고 더 타이트한 버전이 있습니다 (유닉스에 있고 / 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: That should be used instead of the accepted answer at a minimum, in all cases, in order to ensure the update is atomic and concurrent readers will not see truncated files. As I mentioned above, creating the Tempfile in the same directory as the edited file is important here to avoid cross device mv operations being translated into cp operations if /tmp is mounted on a different device. Calling fdatasync is an added layer of paranoia, but it will incur a performance hit, so I omitted it from this example since it is not commonly practiced.


실제로 파일을 제자리에서 편집하는 방법은 없습니다. 파일이 너무 크지 않은 경우 (예 : 파일이 너무 크지 않은 경우) 일반적으로 수행하는 작업은 파일을 메모리로 읽고 ( 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'를 ''로 변경하십시오.


다음은 주어진 디렉토리의 모든 파일에서 찾기 / 바꾸기를위한 솔루션입니다. 기본적으로 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

이것은 나를 위해 작동합니다.

filename = "foo"
text = File.read(filename) 
content = text.gsub(/search_regexp/, "replacestring")
File.open(filename, "w") { |file| file << content }

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(')인용 문자를 인용 단지 멋진 방법입니다.


이번에는 대본에서 짐의 한 라이너에 대한 대안입니다.

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

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

반응형