Computer >> 컴퓨터 >  >> 프로그래밍 >> Ruby

Ruby Tempfile 완벽 가이드: 임시 파일 생성부터 삭제 시점까지

임시 파일(temporary file)을 생성하면 운영체제(OS)의 임시 폴더 안에 무작위 이름을 가진 빈 파일이 하나 만들어집니다.

그리고 이 파일은 자동으로 삭제됩니다.

그렇다면 Ruby에서는 어떻게 할 수 있을까요?

다음과 같이 하면 됩니다:

require 'tempfile'

Tempfile.create { |f| f << "abc\n" }

여기서 f는 생성된 파일 객체이고, << 연산자를 통해 파일에 데이터를 작성합니다.

이 기능은 Ruby에 기본 내장되어 있기 때문에 별도의 젬(gem)을 설치할 필요가 없습니다.

아주 간단하죠?

그럼에도 불구하고 몇 가지 궁금증이 남을 수 있습니다.

예를 들면:

  • 파일은 정확히 언제 삭제될까요?
  • 임시 파일에서 다시 내용을 읽을 수 없는 이유는 무엇일까요?
  • 임시 파일의 이름은 항상 고유하다고 보장할 수 있을까요?

이 글에서는 위 질문들과 더불어 추가적인 내용까지 다루면서, 임시 파일에 대한 더 깊은 이해를 도와드리겠습니다!

new와 create의 차이점은 무엇일까요?

잘 살펴보면 Tempfile에는 파일을 생성하는 두 가지 메서드가 있다는 것을 알 수 있습니다.

하나는 new이고, 다른 하나는 create입니다.

무슨 차이가 있을까요?

공식 문서에 따르면 new는 예상대로 Tempfile 객체를 생성하고, createFile 객체를 반환합니다.

사실 이 부분은 크게 중요하지 않습니다. Tempfile이 내부적으로 File에 위임(delegate)하기 때문입니다.

진짜 차이점은 create는 블록을 인자로 받을 수 있지만, new는 그렇지 않다는 것입니다.

직접 확인해 보세요:

Tempfile.new {}

그러면 다음과 같은 유용한 경고 메시지가 출력됩니다:

# warning: Tempfile.new doesn't call the given block.

create와 블록을 함께 사용하면 블록 실행이 끝난 후 임시 파일이 반드시 삭제되도록 보장할 수 있습니다.

그렇다면 이런 의문이 생깁니다…

임시 파일은 언제 삭제될까요?

파일이 삭제되는 시점은 직접 제어할 수 있습니다. 일반 파일처럼 delete 메서드를 호출하거나, create에 블록을 사용하면 됩니다.

자동 삭제가 이루어지는 경우는 다음과 같습니다:

  • 프로그램이 종료될 때
  • 파일이 "가비지 컬렉션(garbage collection)"될 때, 즉 공간 확보를 위해 알고리즘에 의해 파일이 메모리에서 제거될 때

후자의 경우는 해당 파일에 대한 참조가 전혀 없을 때만 발생합니다.

예를 들어 변수처럼요.

다음 예시를 살펴보세요:

t = Tempfile.new

변수 t가 범위(scope)를 벗어나면 임시 파일이 제거될 수 있습니다.

Linux를 사용하고 있다면, 실시간으로 어떤 파일들이 생성·삭제되는지 관찰할 수 있는 흥미로운 도구인 inotify-tools를 활용해 보세요.

다음 명령어를 실행해 보세요:

inotifywait /tmp -m --format "%w %e %f %T" --timefmt "%H:%m:%S"

그다음 파일을 생성하는 Ruby 코드를 실행합니다.

예시:

ruby -rtempfile -e "def c; t = Tempfile.new('testing'); end; c; sleep 10"

그러면 다음과 같은 결과를 볼 수 있습니다:

/tmp/ CREATE testing20190506-11391-1wqcng0 14:51:48
/tmp/ OPEN testing20190506-11391-1wqcng0 14:51:48
/tmp/ CLOSE_WRITE,CLOSE testing20190506-11391-1wqcng0 14:51:58
/tmp/ DELETE testing20190506-11391-1wqcng0 14:51:58

임시 파일에서 다시 읽을 수 없는 이유는?

임시 파일에 데이터를 쓴 후 다시 읽으려고 하면 빈 문자열이 반환됩니다.

예시:

Tempfile.create { |f| f << "abc\n"; f.read }
# ""

왜 그럴까요?

사실 FileIO 객체입니다.

IO 객체에는 위치 포인터(position pointer)가 있으며, 파일에 쓰기를 할 때마다 이 포인터가 앞으로 이동합니다.

따라서 내용을 읽고 싶다면 먼저 이 포인터를 되감아야(rewind) 합니다.

다음과 같이 하면 됩니다:

Tempfile.create { |f| f << "abc\n"; f.rewind; f.read }
# "abc\n"

또한 파일 내용을 flush해야 할 필요가 있을 수도 있습니다.

예시:

temp = Tempfile.new

temp << "1"
temp << "2"

temp.flush

임시 파일 이름은 정말 고유할까요?

Tempfile은 고유한 파일명을 생성하고 권한 모드를 600으로 설정합니다. 즉, 파일을 생성한 사용자만 해당 파일을 읽을 수 있습니다.

그런데 파일명이 중복될 가능성은 과연 없을까요?

공식 문서에는 다음과 같이 나와 있습니다:

“Tempfile의 파일명 선택 방식은 스레드 세이프(thread-safe)이자 프로세스 간 세이프(inter-process-safe)입니다. 다른 스레드나 프로세스가 동일한 파일명을 선택하는 일이 없음을 보장합니다.”

또한 new의 설명에는 고유한 파일명을 찾지 못할 경우 에러가 발생할 수 있다고 명시되어 있습니다.

파일에 접두사(prefix)를 설정할 수 있다는 점도 알아두면 유용합니다.

다음과 같습니다:

Tempfile.new("banana-").path
# "/tmp/banana-20190426-25403-1cm7sjt"

이렇게 하면 파일명 충돌 가능성을 더욱 줄일 수 있습니다.

바이너리 모드로 임시 파일 열기

이미지, 음악 등 일반 텍스트가 아닌 데이터를 다룰 때는 파일 모드를 바이너리(binary)로 설정하는 것이 좋습니다.

바이너리 모드는 줄바꿈 문자(line-ending) 변환을 방지해 줍니다.

결과적으로 데이터가 더 "원본 그대로(raw)" 처리되며, 바이너리 파일의 일부가 손상되는 것을 피할 수 있습니다.

바이너리 모드는 기본적으로 활성화되어 있지 않습니다:

temp = Tempfile.new

temp.binmode?
# false

다음과 같이 활성화할 수 있습니다:

temp = Tempfile.new
temp.binmode

temp.binmode?
# true

create 메서드에서도 마찬가지로 적용됩니다:

Tempfile.create do |f|
  f.binmode

  f << "bacon\n"
end

마무리

지금까지 Tempfile 클래스를 활용한 Ruby의 임시 파일 처리 방법을 배웠습니다!

이제 여러분이 직접 실습해 볼 차례입니다.

읽어주셔서 감사합니다 🙂