파일 포트

파일 포트 (File Ports)

파일을 열어 읽고 쓰는 파일 스트림 포트를 알아봐요. open-input-file, open-output-file부터 call-with/with- 계열 보조 함수, 파일 잠금까지 다룹니다.

출처: Racket Reference

본문

13.1.5 파일 포트

open-input-file, open-output-file, subprocess와 관련 함수가 만든 포트는 파일 스트림 포트(file-stream port)예요. racket의 초기 입력·출력·오류 포트도 파일 스트림 포트입니다. file-stream-port? 술어로 파일 스트림 포트를 인식해요.

입력 또는 출력 파일 스트림 포트가 만들어지면, 현재 custodian의 관리 아래 놓입니다 (〔Custodians〕 참고). 출력 포트의 경우, 포트를 flush하기 위해 flush callbackcurrent plumber에 등록됩니다.

(open-input-file path [#:mode mode-flag #:for-module? for-module?]) → input-port?
path        : path-string?
mode-flag   : (or/c 'binary 'text) = 'binary
for-module? : any/c = #f

path가 지정하는 파일을 입력용으로 엽니다. mode-flag 인자는 파일의 바이트가 입력에서 어떻게 변환되는지 지정해요:

  • 'binary — 파일에서 읽은 그대로 바이트를 포트에서 반환합니다.

  • 'text — 파일에서 읽은 리턴(return)과 라인피드(linefeed) 바이트(10과 13)를 포트가 플랫폼별 방식으로 필터링합니다:

    • Unix와 Mac OS: 필터링이 일어나지 않습니다.

    • Windows: 파일의 리턴-라인피드 조합을 포트가 단일 라인피드로 반환합니다. 라인피드가 뒤따르지 않는 리턴 바이트나 리턴이 앞서지 않는 라인피드에는 필터링이 일어나지 않습니다.

Windows에서 'text 모드는 일반 파일에서만 동작해요. 다른 종류의 파일에 'text를 쓰려 하면 exn:fail:filesystem 예외가 일어납니다.

그 외에는 path가 지정하는 파일이 일반 파일일 필요가 없어요. 그것은 파일 시스템을 통해 연결된 장치일 수 있는데, 예를 들어 Windows의 "aux"나 Unix의 "/dev/null"같은 것이죠. 어떤 경우든 포트는 기본적으로 버퍼링됩니다.

open-input-file이 만든 포트는 OS 수준 파일 핸들을 해제하기 위해 close-input-port로 직접, 또는 custodian-shutdown-all을 통해 간접적으로 명시적으로 닫아야 해요. 입력 포트는 그 외에 쓰레기 수집이 가능해지더라도 자동으로 닫히지 않습니다 (〔Garbage Collection〕 참고). will을 입력 포트와 연결해 더 자동으로 닫게 할 수 있어요 (〔Wills and Executors〕 참고).

pathcleanse된 버전인 path 값이 열린 포트의 이름으로 사용됩니다.

O_CLOEXEC를 지원하는 Unix와 MacOS 변형에서는 파일이 O_CLOEXEC로 열려서, 기저 파일 디스크립터가 subprocess가 만든 하위 프로세스와 공유되지 않습니다. Windows에서는 파일이 상속되지 않는 핸들로 열립니다.

파일 시스템 오류 때문에 파일을 여는 데 실패하면 exn:fail:filesystem:errno 예외가 일어나는데, 단 for-module?#f이거나, current-module-path-for-load이 non-#f 값을 갖거나, 파일 시스템 오류가 파일-없음 오류로 인식되지 않는 경우예요. 그 외에 for-module?가 참이고 current-module-path-for-load이 non-#f 값을 가지며 파일 시스템 오류가 파일-없음 오류로 인식되면, 일어나는 예외는 current-module-path-for-load의 값이 syntax object이면 exn:fail:syntax:missing-module, 아니면 exn:fail:filesystem:missing-module입니다.

버전 6.0.1.6의 package base에서 변경됨: #:for-module? 추가.

버전 8.11.1.6에서 변경됨: 운영 체제가 지원하는 곳에서 O_CLOEXEC를 사용하도록 변경.

예시:

(with-output-to-file some-file
  (lambda () (printf "hello world")))
(define in (open-input-file some-file))
(read-string 11 in)
; "hello world"
(close-input-port in)
(open-output-file path [#:mode mode-flag #:exists exists-flag
                       #:permissions permissions
                       #:replace-permissions? replace-permissions?])
  → output-port?
path                 : path-string?
mode-flag            : (or/c 'binary 'text) = 'binary
exists-flag          : (or/c 'error 'append 'update 'can-update
                             'replace 'truncate 'must-truncate 'truncate/replace) = 'error
permissions          : (integer-in 0 65535) = #o666
replace-permissions? : any/c = #f

path가 지정하는 파일을 출력용으로 엽니다. mode-flag 인자는 포트에 쓰인 바이트가 파일에 쓰일 때 어떻게 변환되는지 지정해요:

  • 'binary — 포트에 쓰인 그대로 바이트를 파일에 씁니다.

  • 'text — Windows에서, 포트에 쓰인 라인피드 바이트(10)는 파일에서 리턴-라인피드 조합으로 변환됩니다. 리턴에는 필터링이 일어나지 않습니다.

Windows에서 'text 모드는 일반 파일에서만 동작해요. 다른 종류의 파일에 'text를 쓰려 하면 exn:fail:filesystem 예외가 일어납니다.

exists-flag 인자는 이미 존재하는 파일을 어떻게 처리/요구할지 지정해요:

  • 'error — 파일이 존재하면 exn:fail:filesystem을 일으킵니다.

  • 'replace — 기존 파일이 있다면 제거하고 새 파일을 씁니다.

  • 'truncate — 파일이 존재하면 기존 데이터를 모두 제거합니다.

  • 'must-truncate — 기존 파일의 모든 데이터를 제거합니다. 파일이 존재하지 않으면 exn:fail:filesystem 예외가 일어납니다.

  • 'truncate/replace'truncate를 시도합니다. (아마 파일 권한 때문에) 실패하면 'replace를 시도합니다.

  • 'update — 기존 파일을 절삭하지 않고 엽니다. 파일이 존재하지 않으면 exn:fail:filesystem 예외가 일어납니다. 현재 읽기/쓰기 위치를 바꾸려면 file-position을 사용하세요.

  • 'can-update — 기존 파일을 절삭하지 않고 열거나, 존재하지 않으면 파일을 만듭니다.

  • 'append — 존재 여부와 무관하게 파일 끝에 덧붙입니다. Windows에서 'append'update와 동등하지만, 파일이 존재할 필요가 없고 파일을 연 직후 파일 위치가 즉시 파일 끝으로 설정됩니다.

path가 지정하는 파일이 생성될 때, permissions는 생성된 파일의 권한을 지정하며, 권한의 정수 표현은 file-or-directory-permissions과 동일하게 취급됩니다. Unix와 Mac OS에서는 이 권한 비트가 프로세스의 umask와 결합됩니다. Windows에서는 permissions의 유일한 관련 속성은 쓰기 권한을 위한 #o2 비트가 설정되어 있는지 여부예요. open-output-file로 읽기 전용 파일을 만들 수 있는데, 그 경우 나중에 파일을 열려는 시도만 쓰기가 금지된다는 점을 유의하세요. replace-permissions?가 참 값이면, 열린 파일이 새로 생성되었는지와 무관하게 permissions 값이 열린 파일에 적용되며, Unix와 Mac OS에서 프로세스의 umask와 무관하게 적용됩니다.

path가 지정하는 파일이 일반 파일일 필요는 없어요. 그것은 파일 시스템을 통해 연결된 장치일 수 있는데, 예를 들어 Windows의 "aux"나 Unix의 "/dev/null"같은 것이죠. 출력 포트는 기본적으로 블록 버퍼링이 되는데, 파일이 터미널에 해당하면 기본적으로 라인 버퍼링이 됩니다. Unix와 Mac OS에서 파일이 fifo라면, 포트는 fifo의 리더가 생길 때까지 쓰기용으로 블록됩니다 (port-waiting-peer? 참고).

open-output-file이 만든 포트는 OS 수준 파일 핸들을 해제하기 위해 close-output-port로 직접, 또는 custodian-shutdown-all을 통해 간접적으로 명시적으로 닫아야 해요. 출력 포트는 그 외에 쓰레기 수집이 가능해지더라도 자동으로 닫히지 않습니다 (〔Garbage Collection〕 참고). will을 출력 포트와 연결해 더 자동으로 닫게 할 수 있어요 (〔Wills and Executors〕 참고).

pathcleanse된 버전인 path 값이 열린 포트의 이름으로 사용됩니다.

O_CLOEXEC를 지원하는 Unix와 MacOS 변형에서는 파일이 O_CLOEXEC로 열려서, 기저 파일 디스크립터가 subprocess가 만든 하위 프로세스와 공유되지 않습니다. Windows에서는 파일이 상속되지 않는 핸들로 열립니다.

기저 파일 시스템의 오류 때문에 파일을 여는 데 실패하면 exn:fail:filesystem:errno 예외가 일어납니다.

예시:

(define out (open-output-file some-file))
(write "hello world" out)
(close-output-port out)

버전 6.9.0.6의 package base에서 변경됨: Unix와 Mac OS에서 권한 오류 시 'truncate/replace가 교체하도록 변경. Windows에서 'replace'truncate/replace처럼 절삭하는 대신 항상 교체하도록 변경.

버전 7.4.0.5에서 변경됨: Unix와 Mac OS에서 fifo 처리 변경 — fifo에 리더가 생길 때까지 포트가 출력용으로 블록되도록 변경.

버전 8.1.0.3에서 변경됨: #:permissions 인자 추가.

버전 8.7.0.10에서 변경됨: #:replace-permissions? 인자 추가.

버전 8.11.1.6에서 변경됨: 운영 체제가 지원하는 곳에서 O_CLOEXEC를 사용하도록 변경.

(open-input-output-file path [#:mode mode-flag #:exists exists-flag
                             #:permissions permissions
                             #:replace-permissions? replace-permissions?])
  → input-port? output-port?
path                 : path-string?
mode-flag            : (or/c 'binary 'text) = 'binary
exists-flag          : (or/c 'error 'append 'update 'can-update
                             'replace 'truncate 'must-truncate 'truncate/replace) = 'error
permissions          : (integer-in 0 65535) = #o666
replace-permissions? : any/c = #f

open-output-file과 같지만, 입력 포트와 출력 포트 두 값을 만들어요. 두 포트는 기저 파일 디스크립터를 공유한다는 점에서 연결됩니다. 이 프로시저는 Windows의 "COM1"처럼 한 프로세스만 열 수 있는 특수 장치에 쓰기 위한 것이에요. 일반 파일에서는 파일 디스크립터 공유가 혼란스러울 수 있어요. 예를 들어 한 포트를 사용한다고 해서 다른 포트의 버퍼가 자동으로 flush되지 않고, 한 포트에서 읽거나 쓰면 다른 포트의 파일 위치(있다면)가 움직입니다. 일반 파일에서는 혼란을 피하기 위해 별도의 open-input-fileopen-output-file 호출을 사용하세요.

버전 8.1.0.3의 package base에서 변경됨: #:permissions 인자 추가.

버전 8.7.0.10에서 변경됨: #:replace-permissions? 인자 추가.

(call-with-input-file path proc [#:mode mode-flag]) → any
path      : path-string?
proc      : (input-port? . -> . any)
mode-flag : (or/c 'binary 'text) = 'binary

pathmode-flag 인자로 open-input-file을 호출하고, 결과 포트를 proc에 전달합니다. call-with-input-file 호출의 결과는 proc의 결과지만, proc이 반환할 때 새로 열린 포트는 닫힙니다.

예시:

(with-output-to-file some-file
  (lambda () (printf "text in a file")))
(call-with-input-file some-file
  (lambda (in) (read-string 14 in)))
; "text in a file"
(call-with-output-file path proc [#:mode mode-flag #:exists exists-flag
                                 #:permissions permissions
                                 #:replace-permissions? replace-permissions?])
  → any
path                 : path-string?
proc                 : (output-port? . -> . any)
mode-flag            : (or/c 'binary 'text) = 'binary
exists-flag          : (or/c 'error 'append 'update 'can-update
                             'replace 'truncate 'must-truncate 'truncate/replace) = 'error
permissions          : (integer-in 0 65535) = #o666
replace-permissions? : any/c = #f

call-with-input-file과 유사하지만, path, mode-flag, exists-flag, permissions, replace-permissions?open-output-file에 전달합니다.

예시:

(call-with-output-file some-file
  (lambda (out)
    (write 'hello out)))
(call-with-input-file some-file
  (lambda (in)
    (read-string 5 in)))
; "hello"

버전 8.1.0.3의 package base에서 변경됨: #:permissions 인자 추가.

버전 8.7.0.10에서 변경됨: #:replace-permissions? 인자 추가.

(call-with-input-file* path proc [#:mode mode-flag]) → any
path      : path-string?
proc      : (input-port? . -> . any)
mode-flag : (or/c 'binary 'text) = 'binary

call-with-input-file과 같지만, 새로 열린 포트는 call-with-input-file* 호출의 다이내믹 범위(dynamic extent)에서 제어가 벗어날 때마다 닫힙니다 — proc의 반환이든, continuation 적용이든, prompt 기반 abort든 상관없이요.

(call-with-output-file* path proc [#:mode mode-flag #:exists exists-flag
                                  #:permissions permissions
                                  #:replace-permissions? replace-permissions?])
  → any
path                 : path-string?
proc                 : (output-port? . -> . any)
mode-flag            : (or/c 'binary 'text) = 'binary
exists-flag          : (or/c 'error 'append 'update 'can-update
                             'replace 'truncate 'must-truncate 'truncate/replace) = 'error
permissions          : (integer-in 0 65535) = #o666
replace-permissions? : any/c = #f

call-with-output-file과 같지만, 새로 열린 포트는 call-with-output-file* 호출의 다이내믹 범위에서 제어가 벗어날 때마다 닫힙니다 — proc의 반환이든, continuation 적용이든, prompt 기반 abort든 상관없이요.

버전 8.1.0.3의 package base에서 변경됨: #:permissions 인자 추가.

버전 8.7.0.10에서 변경됨: #:replace-permissions? 인자 추가.

(with-input-from-file path thunk [#:mode mode-flag]) → any
path      : path-string?
thunk     : (-> any)
mode-flag : (or/c 'binary 'text) = 'binary

call-with-input-file*과 같지만, 새로 열린 포트를 주어진 프로시저 인자에 전달하는 대신, thunk 호출 주위에서 parameterize를 사용해 포트를 현재 입력 포트(〔current-input-port〕 참고)로 설치합니다.

예시:

(with-output-to-file some-file
  (lambda () (printf "hello")))
(with-input-from-file some-file
  (lambda () (read-string 5)))
; "hello"
(with-output-to-file path thunk [#:mode mode-flag #:exists exists-flag
                                #:permissions permissions
                                #:replace-permissions? replace-permissions?])
  → any
path                 : path-string?
thunk                : (-> any)
mode-flag            : (or/c 'binary 'text) = 'binary
exists-flag          : (or/c 'error 'append 'update 'can-update
                             'replace 'truncate 'must-truncate 'truncate/replace) = 'error
permissions          : (integer-in 0 65535) = #o666
replace-permissions? : any/c = #f

call-with-output-file*과 같지만, 새로 열린 포트를 주어진 프로시저 인자에 전달하는 대신, thunk 호출 주위에서 parameterize를 사용해 포트를 현재 출력 포트(〔current-output-port〕 참고)로 설치합니다.

예시:

(with-output-to-file some-file
  (lambda () (printf "hello")))
(with-input-from-file some-file
  (lambda () (read-string 5)))
; "hello"

버전 8.1.0.3의 package base에서 변경됨: #:permissions 인자 추가.

버전 8.7.0.10에서 변경됨: #:replace-permissions? 인자 추가.

(port-try-file-lock? port mode) → boolean?
port : file-stream-port?
mode : (or/c 'shared 'exclusive)

현재 플랫폼의 파일 잠금 시설을 사용해 파일에 대한 잠금(lock)을 획득하려 시도합니다. 여러 프로세스가 파일에 'shared 잠금을 획득할 수 있지만, 최대 한 프로세스만 'exclusive 잠금을 보유할 수 있고, 'shared'exclusive 잠금은 상호 배타적이에요. mode'shared이면 port는 입력 포트여야 하고, mode'exclusive이면 port는 출력 포트여야 합니다.

요청된 잠금을 획득하면 결과는 #t, 그렇지 않으면 #f입니다. 잠금이 획득되면 port-file-unlock으로 해제되거나(프로세스 종료 때문에) 포트가 닫힐 때까지 유지됩니다.

플랫폼에 따라 잠금은 단지 권고적(advisory)일 수 있거나(즉, 잠금은 프로세스가 잠금을 획득하는 능력에만 영향을 줌), 잠긴 파일에 대한 읽기와 쓰기를 막는 강제적(mandatory) 잠금에 해당할 수 있어요. 구체적으로, 잠금은 Windows에서 강제적이고 다른 플랫폼에서는 권고적입니다. 단일 포트에 대한 'shared 잠금의 여러 시도는 성공할 수 있어요. Unix와 Mac OS에서는 단일 port-file-unlock이 잠금을 해제하지만, Windows에서는 성공한 각 port-try-file-lock?마다 port-file-unlock이 필요합니다. Unix와 Mac OS에서는 'exclusive 잠금의 여러 시도가 성공할 수 있고 단일 port-file-unlock이 잠금을 해제하지만, Windows에서는 포트가 이미 잠금을 보유하고 있으면 그 포트에 대한 'exclusive 잠금 시도가 실패해요.

open-input-output-file의 입력 포트에 대해 획득한 잠금은 대응하는 출력 포트의 port-file-unlock으로 해제될 수 있고, 그 반대도 마찬가지예요. open-input-output-file의 출력 포트가 'exclusive 잠금을 보유하고 있으면 대응하는 입력 포트는 여전히 'shared 잠금을, 심지어 여러 번 획득할 수 있어요. Windows에서는 성공한 각 잠금 시도마다 port-file-unlock이 필요한 반면, Unix와 Mac OS에서는 단일 port-file-unlock이 잠금 시도를 상쇄합니다. Unix와 Mac OS에서는 입력 포트의 'shared 잠금을 대응하는 출력 포트를 통해 'exclusive 잠금으로 승격할 수 있는데, 그 경우 단일 port-file-unlock(어느 포트든)이 잠금을 해제하는 반면 Windows에서는 이런 승격이 허용되지 않습니다.

잠금은 보통 파일 포트에만 지원되고, 다른 종류의 파일 스트림 포트에 잠금 획득을 시도하면 exn:fail:filesystem 예외가 일어납니다.

(port-file-unlock port) → void?
port : file-stream-port?

현재 프로세스가 port의 파일에 보유한 잠금을 해제합니다.

(port-file-identity port) → exact-positive-integer?
port : file-stream-port?

port가 읽거나 쓰는 장치와 파일의 정체성을 나타내는 숫자를 반환합니다. 열린 시간이 겹치는 두 포트의 경우, 두 포트가 같은 장치와 파일에 접근하면 port-file-identity의 결과가 두 포트 모두에 대해 같고, 그때만 같아요. 열린 시간이 겹치지 않는 포트의 경우, (실제로 두 포트가 같은 파일에 접근하더라도) 다른 포트와의 관계를 통해 추론할 수 있는 경우를 제외하고는 포트 정체성에 대한 보장이 없습니다. port가 닫혀 있으면 exn:fail 예외가 일어납니다. Windows 95, 98, Me에서 port가 파일이 아닌 파이프에 연결되어 있으면 exn:fail:filesystem 예외가 일어납니다.

예시:

(define file1 (open-output-file some-file))
(define file2 (open-output-file some-other-file))
(port-file-identity file1)
; 312109594121408390019218690
(port-file-identity file2)
; 312109612568152463728770306
(close-output-port file1)
(close-output-port file2)
(port-file-stat port) → (and/c (hash/c symbol? any/c) hash-eq?)
port : file-stream-port?

file-or-directory-stat과 같지만, 파일의 경로를 사용하는 대신 포트가 나타내는 열린 파일의 정보를 반환합니다.

버전 8.15.0.6의 package base에서 추가됨.

더 알아보기