IO::Handle — 열린 파일 또는 스트림
IO::Handle — 열린 파일 또는 스트림
IO::Handle의 인스턴스는 입출력 리소스를 조작하기 위한 **핸들(handle)**을 캡슐화해요. 보통 직접 IO::Handle 인스턴스를 만들 필요는 없어요. 다른 롤과 메서드가 알아서 만들어주기 때문이죠. 예를 들어 IO::Path 객체는 IO::Handle을 반환하는 open 메서드를 제공해요.
my $fh = '/tmp/log.txt'.IO.open;
say $fh.^name; # OUTPUT: IO::Handle
첫 줄은 대략 다음 코드와 동일해요.
my $fh = IO::Handle.new( :path( '/tmp/log.txt'.IO.path ) ).open;
본문
메서드 (Methods)
method open
method open(IO::Handle:D:
:$r, :$w, :$x, :$a, :$update,
:$rw, :$rx, :$ra,
:$mode is copy,
:$create is copy,
:$append is copy,
:$truncate is copy,
:$exclusive is copy,
:$bin,
:$enc is copy,
:$chomp = $!chomp,
:$nl-in is copy = $!nl-in,
Str:D :$nl-out is copy = $!nl-out,
:$out-buffer is copy,
)
여러 모드 중 하나로 핸들을 열어요. 열기가 실패하면 적절한 예외로 fail해요.
:$chomp, :$nl-in, :$nl-out, :$enc에 대해 허용되는 값과 동작은 각 메서드 설명을 참고하세요. 이 매개변수들의 기본값은 호출 객체의 속성이고, 그중 하나라도 제공되면 속성이 새 값으로 갱신돼요. 핸들을 이진 모드로 열고 싶다면 :$enc 대신 :$bin을 True로 지정하세요. :$enc에 정의되지 않은 값을 지정하는 것은 :$enc를 아예 지정하지 않는 것과 동일해요. 정의된 인코딩을 :$enc로 지정하면서 :$bin을 참으로 설정하면 X::IO::BinaryAndEncoding 예외가 던져져요.
열기 모드의 기본값은 비배타적(non-exclusive), 읽기 전용(:r을 지정한 것과 같음)이고, 다음 인자들의 조합으로 제어할 수 있어요.
:r same as specifying :mode<ro> same as specifying nothing
:w same as specifying :mode<wo>, :create, :truncate
:a same as specifying :mode<wo>, :create, :append
:x same as specifying :mode<wo>, :create, :exclusive
:update same as specifying :mode<rw>
:rw same as specifying :mode<rw>, :create
:ra same as specifying :mode<rw>, :create, :append
:rx same as specifying :mode<rw>, :create, :exclusive
:r 인자는 위 표 아래 세 줄에 보이는 두 글자의 조합과 정확히 같아요. 위에 나열된 것 이외의 모드 조합에 대한 지원은 구현에 의존하며 지원되지 않는다고 간주해야 해요. 즉 예를 들어 .open(:r :create)나 .open(:mode<wo> :append :truncate)를 지정하면 특정 구현에 따라 동작할 수도 있고 우주가 붕괴할 수도 있어요. 이는 그런 지원되지 않는 모드로 열린 핸들에 대한 읽기·쓰기도 마찬가지예요.
모드의 세부 내용은 다음과 같아요.
:mode<ro> means "read only"
:mode<wo> means "write only"
:mode<rw> means "read and write"
:create means the file will be created, if it does not exist
:truncate means the file will be emptied, if it exists
:exclusive means .open will fail if the file already exists
:append means writes will be done at the end of file's current contents
디렉터리를 열려 하거나, 읽기 전용 모드로 열린 핸들에 쓰거나, 쓰기 전용 모드로 열린 핸들에서 읽거나, 이진 모드로 열린 핸들에서 텍스트 읽기 메서드를 쓰려 하면 실패하거나 예외가 던져져요.
6.c 언어에서는 경로 '-'를 열 수 있어요. 그러면 읽기 전용 모드면 $*IN 핸들이 (닫혀 있다면) 열리고, 쓰기 전용 모드면 $*OUT 핸들이 열려요. 이 경우 다른 모든 모드는 예외를 던져요.
6.d 언어 버전부터 경로 '-' 사용은 deprecated이며, 향후 언어 버전에서 완전히 제거될 거예요.
:out-buffer는 출력 버퍼링을 제어하며 기본적으로 Nil인 것처럼 동작해요. 자세한 내용은 out-buffer 메서드를 참고하세요.
참고 (Rakudo 2017.09 이전 버전): 파일핸들은 범위를 벗어나도 flush되거나 닫히지 않아요. 가비지 컬렉션될 때 닫히긴 하지만, 가비지 컬렉션이 실행된다는 보장이 없죠. 즉 데이터 손실을 피하려면 쓰기용으로 연 핸들에는 명시적 close를 해야 하고, 읽기용으로 연 핸들에도 명시적 close를 하는 게 권장돼요. 그래야 프로그램이 한 번에 너무 많은 파일을 열어 이후 open 호출에서 예외가 발생하는 일을 피할 수 있어요.
참고 (Rakudo 2017.09 이후 버전): 열린 파일핸들은 프로그램 종료 시 자동으로 닫히지만, 여전히 연 핸들을 명시적으로 close하는 것을 강력히 권장해요.
method comb
method comb(IO::Handle:D: Bool :$close, |args --> Seq:D)
핸들을 읽고 Str.comb가 하는 것과 같은 방식으로 내용을 처리해요. 같은 인자를 받으며, $close가 참 값으로 설정돼 있으면 끝나면 핸들을 닫아요. 구현은 이 메서드를 호출할 때 파일을 통째로 slurp할 수 있어요.
핸들이 이진 모드일 때 이 메서드를 호출하면 X::IO::BinaryMode 예외가 던져져요.
my $fh = 'path/to/file'.IO.open;
say "The file has {+$fh.comb: '♥', :close} ♥s in it";
method chomp
has $.chomp is rw = True
.new나 open으로 설정할 수 있는 속성 중 하나예요. 기본값은 True. .get이나 .lines 메서드를 쓸 때 .nl-in에 정의된 줄 구분자를 내용에서 제거할지 여부를 지정하는 Bool을 받아요.
routine get
method get(IO::Handle:D: --> Str:D)
multi get (IO::Handle $fh = $*ARGFILES --> Str:D)
핸들에서 입력 한 줄을 읽고, 핸들의 .chomp 속성이 True로 설정돼 있으면 뒤따르는 새 줄 문자(.nl-in으로 설정된)를 제거해요. 더 이상 입력이 없으면 Nil을 반환해요. 서브루틴 형태는 핸들이 주어지지 않으면 기본적으로 $*ARGFILES를 사용해요.
핸들이 이진 모드일 때 이 메서드를 호출하면 X::IO::BinaryMode 예외가 던져져요.
$*IN.get.say; # Read one line from the standard input
my $fh = open 'filename';
$fh.get.say; # Read one line from a file
$fh.close;
say get; # Read one line from $*ARGFILES
routine getc
method getc(IO::Handle:D: --> Str:D)
multi getc (IO::Handle $fh = $*ARGFILES --> Str:D)
입력 스트림에서 한 문자를 읽어요. 핸들이 이진 모드일 때 이 메서드를 호출하면 X::IO::BinaryMode 예외가 던져져요. 서브루틴 형태는 핸들이 주어지지 않으면 기본적으로 $*ARGFILES를 사용해요. 더 이상 입력이 없으면 Nil을 반환하고, 그렇지 않으면 연산이 적어도 한 문자가 준비될 때까지 블록돼요. 다음 주의점들이 적용돼요.
버퍼링된 터미널 (Buffering terminals)
터미널에서 한 번의 키 입력(키프레스)을 getc로 받으려면 터미널을 "unbuffered"로 설정해 둔 경우에만 제대로 동작해요. 그렇지 않으면 컴파일러가 단 한 바이트의 데이터를 받기 전에 터미널이 엔터 키가 눌리거나 버퍼가 차기를 기다려요.
결합 문자 대기 (Waiting for potential combiners)
핸들의 인코딩이 결합 문자를 읽도록 허용하면, raku는 문자를 제공하기 전에 더 많은 데이터가 준비되기를 기다려요. 즉 "e" 다음에 결합 음절 악센트(acute)를 입력하면, "e"를 주고 다음 읽기 함수가 매달린 결합 문자를 주도록 놔두는 대신, 악센트가 붙은 e가 나와요. 하지만 사용자가 그냥 "e"만 입력하고 결합 악센트를 더 입력할 의도가 없으면, 프로그램은 초기 "e"가 반환되기 전에 또 다른 키 입력을 기다리게 된다는 뜻이기도 해요.
submethod DESTROY
submethod DESTROY(IO::Handle:D:)
파일핸들을 닫아요. 단, native-descriptor가 2 이하인 경우는 제외해요. 이렇게 해서 표준 파일핸들이 실수로 닫히지 않도록 보장해요.
가비지 컬렉션이 실행된다는 보장이 없으므로, 쓰기 대상 핸들은 DESTROY에 의존하지 말고 직접 닫아야 해요. 파일을 많이 여는 프로그램은 쓰기용으로 열었는지와 무관하게 핸들을 명시적으로 닫아야 해요. 가비지 컬렉션이 발생해 더 이상 쓰지 않는 핸들이 닫히기 전에 너무 많은 파일이 열릴 수 있기 때문이죠.
method gist
method gist(IO::Handle:D: --> Str:D)
핸들이 어떤 .path를 위해 만들어졌는지(있다면)와 .opened 상태인지에 대한 정보를 담은 문자열을 반환해요.
say IO::Handle.new; # IO::Handle<(Any)>(closed)
say "foo".IO.open; # IO::Handle<"foo".IO>(opened)
method eof
method eof(IO::Handle:D: --> Bool:D)
논블로킹. 읽기 연산이 핸들의 내용을 소진했으면 True를 반환해요. seek 가능한 핸들에서는 현재 위치가 파일 끝에 있거나 그 너머라는 뜻이며, 소진된 핸들을 파일 내용 안으로 seek하면 eof가 다시 False를 반환해요.
seek 불가능한 핸들과 크기가 0인 파일(특수 파일 /proc/ 포함)로 열린 핸들에서는, 읽기 연산이 어떤 바이트도 읽지 못해 실패하기 전까지는 EOF가 설정되지 않아요. 예를 들어 다음 코드에서 첫 번째 read는 모든 데이터를 소비하지만, 아무것도 읽지 못하는 두 번째 read가 되어서야 TTY 핸들에 EOF가 설정돼요.
$ echo "x" | raku -e 'with $*IN { .read: 10000; .eof.say; .read: 10; .eof.say }'
False
True
method encoding
multi method encoding(IO::Handle:D: --> Str:D)
multi method encoding(IO::Handle:D: $enc --> Str:D)
핸들이 현재 사용하는 인코딩을 나타내는 Str을 반환해요. 기본값은 "utf8". Nil은 파일핸들이 현재 이진 모드임을 나타내요. 선택적 위치 인자 $enc를 지정하면 핸들이 사용하는 인코딩을 바꾸고, 인코딩으로 Nil을 지정하면 핸들을 이진 모드로 만들어요.
인코딩에 허용되는 값은 대소문자를 구분하지 않아요. 사용 가능한 인코딩은 구현과 백엔드에 따라 달라져요. Rakudo MoarVM에서는 다음이 지원돼요.
utf8
utf16
utf16le
utf16be
utf8-c8
iso-8859-1
windows-1251
windows-1252
windows-932
ascii
기본 인코딩은 utf8이고, 유니코드 NFC(정규화 형식 표준 형식)로 정규화돼요. 어떤 경우에는 정규화가 되지 않도록 하고 싶을 수 있는데, 그럴 때 utf8-c8를 쓸 수 있어요. utf8-c8를 쓰기 전에 utf8-c8와 NFC에 대한 자세한 내용은 유니코드 문서를 읽어보세요.
Rakudo 2018.04부터 ShiftJIS의 변형인 windows-932도 지원돼요.
구현은 별칭(alias)도 지원할 수 있어요. 예를 들어 Rakudo는 iso-8859-1 인코딩에 latin-1, 대시 붙은 utf 버전 utf-8와 utf-16 같은 별칭을 허용해요.
utf16, utf16le and utf16be
utf8과 달리 utf16에는 endianness(빅 엔디언 또는 리틀 엔디언)가 있어요. 이것은 바이트의 순서와 관련돼요. 컴퓨터 CPU도 endianness를 가지죠. Raku의 utf16 형식 지정자는 인코딩할 때 호스트 시스템의 endianness를 사용해요. 디코딩할 때는 바이트 순서 표시(byte order mark)를 찾아, 있다면 그것으로 endianness를 설정해요. 바이트 순서 표시가 없으면 파일이 호스트 시스템과 같은 endianness를 사용한다고 가정해요. 바이트 순서 표시는 codepoint U+FEFF, 즉 ZERO WIDTH NO-BREAK SPACE예요. utf16로 인코딩된 파일에서는 표준에 따라 파일 시작에 존재하면 U+FEFF codepoint가 아니라 바이트 순서 표시로 해석돼요.
다른 endian 시스템에서 쓰면 다른 파일이 쓰여지지만, 2018.10 릴리스에서는 파일을 쓸 때 바이트 순서 표시가 쓰여지고 utf16 인코딩으로 만든 파일은 빅 또는 리틀 엔디언 시스템 어디서든 읽을 수 있게 될 거예요.
utf16be나 utf16le 인코딩을 사용할 때는 바이트 순서 표시가 사용되지 않아요. 사용되는 endianness는 호스트 CPU 타입의 영향을 받지 않으며, utf16be는 빅 엔디언, utf16le는 리틀 엔디언이에요.
표준을 따르기 위해 파일 시작의 0xFEFF 바이트는 바이트 순서 표시가 아니라 ZERO WIDTH NO-BREAK SPACE로 해석돼요. utf16be 또는 utf16le 인코딩을 사용하는 파일에는 바이트 순서 표시가 쓰여지지 않아요.
MoarVM에서 Rakudo 2018.09부터 utf16, utf16le, utf16be가 지원돼요. 2018.10에서는 utf16으로 파일을 쓰면 바이트 순서 표시(BOM)가 제대로 추가돼요.
예시 (Examples)
with 'foo'.IO {
.spurt: "First line is text, then:\nBinary";
my $fh will leave {.close} = .open;
$fh.get.say; # OUTPUT: «First line is text, then:»
$fh.encoding: Nil;
$fh.slurp.say; # OUTPUT: «Buf[uint8]:0x<42 69 6e 61 72 79>»
}
routine lines
sub lines( $what = $*ARGFILES, |c)
multi method lines( IO::Handle:D: $limit, :$close )
multi method lines( IO::Handle:D: :$close )
기본적으로 $*ARGFILES를 받는 서브 형태는 첫 번째 인자인 객체에 lines 메서드를 적용하고 나머지 인자를 전달해요.
이 메서드는 핸들에서 온 줄(.nl-in으로 구분된 청크) 각각을 요소로 가진 Seq를 반환해요. 핸들의 .chomp 속성이 True로 설정돼 있으면 각 줄에서 .nl-in으로 지정된 문자들이 제거돼요.
최대 $limit 줄을 읽어요. 여기서 $limit는 음이 아닌 Int, Inf, 또는 Whatever(Inf를 의미하는 것으로 해석)일 수 있어요. :$close가 True로 설정돼 있으면 파일이 끝나거나 $limit에 도달했을 때 핸들을 닫아요. 서브루틴 형태는 핸들이 주어지지 않으면 기본적으로 $*ARGFILES를 사용해요.
핸들이 이진 모드일 때 이 메서드를 호출하면 X::IO::BinaryMode 예외가 던져져요.
참고: 줄은 느긋하게(lazily) 읽히므로, 핸들을 닫거나 파일 위치를 바꾸는 다른 메서드를 쓰려 할 때 반환된 Seq가 완전히 reify되어 있거나 더 이상 필요하지 않도록 해야 해요.
say "The file contains ",
'50GB-file'.IO.open.lines.grep(*.contains: 'Raku').elems,
" lines that mention Raku";
# OUTPUT: «The file contains 72 lines that mention Raku»
lines를 /proc/* 파일(6.d 버전부터)에서도 쓸 수 있어요.
say lines( "/proc/$*PID/statm".IO ); # OUTPUT: «(58455 31863 8304 2 0 29705 0)»
method lock
method lock(IO::Handle:D:
Bool:D :$non-blocking = False, Bool:D :$shared = False
--> True)
파일핸들이 열려 있는 파일에 조언적(advisory) 잠금을 걸어요. :$non-blocking이 True면 잠금을 얻지 못했을 때 X::IO::Lock으로 fail하고, 그렇지 않으면 잠금을 걸 수 있을 때까지 블록해요. :$shared가 True면 공유(읽기) 잠금을, 아니면 배타적(쓰기) 잠금을 걸어요. 성공하면 True를 반환하고, 잠금을 걸 수 없으면 X::IO::Lock으로 fail해요(예: 쓰기 모드로 열린 파일핸들에 공유 잠금을 걸거나, 읽기 모드로 열린 파일핸들에 배타적 잠금을 거는 경우).
.lock을 다시 사용해 기존 잠금을 다른 잠금으로 교체할 수도 있어요. 잠금을 제거하려면 파일핸들을 close하거나 unlock을 쓰세요.
# One program writes, the other reads, and thanks to locks either
# will wait for the other to finish before proceeding to read/write
# Writer
given "foo".IO.open(:w) {
.lock;
.spurt: "I ♥ Raku!";
.close; # closing the handle unlocks it; we could also use `unlock` for that
}
# Reader
given "foo".IO.open {
.lock: :shared;
.slurp.say; # OUTPUT: «I ♥ Raku!»
.close;
}
method unlock
method unlock(IO::Handle:D: --> True)
파일핸들에서 lock을 제거해요. 가능하면 True를 반환하고, 불가능하면 예외로 fail해요.
routine words
multi words(IO::Handle:D $fh = $*ARGFILES, $limit = Inf, :$close --> Seq:D)
multi method words(IO::Handle:D: $limit = Inf, :$close --> Seq:D)
Str.words와 비슷하게, 핸들의 스트림을 연속된 공백(유니코드가 정의한) 청크로 나누고 결과 "단어"들의 Seq를 반환해요. 선택적 $limit 인자를 받는데, 이는 음이 아닌 Int, Inf, 또는 Whatever(Inf로 해석)일 수 있으며, 최대 $limit 개의 단어만 반환해야 함을 나타내요. Bool인 :$close 명명 인자가 True로 설정돼 있으면 반환된 Seq가 소진될 때 핸들을 자동으로 닫아요. 서브루틴 형태는 핸들이 주어지지 않으면 기본적으로 $*ARGFILES를 사용해요.
핸들이 이진 모드일 때 이 메서드를 호출하면 X::IO::BinaryMode 예외가 던져져요.
my %dict := bag $*IN.words;
say "Most common words: ", %dict.sort(-*.value).head: 5;
참고: 구현은 .words 호출 시 필요한 것보다 더 많은 데이터를 읽을 수 있어요. 즉 $handle.words(2)는 두 단어 분량보다 더 많은 데이터를 읽을 수 있고, 이후 읽기 메서드 호출이 fetch된 두 단어 바로 뒤 지점에서 읽지 않을 수도 있어요. .words 호출 후에는 파일 위치를 정의되지 않은 것으로 취급해야 해요.
method split
method split(IO::Handle:D: :$close, |c)
핸들의 내용을 slurp하고 그 내용에 Str.split을 호출하며 주어진 인자를 모두 전달해요. :$close 명명 매개변수가 True로 설정돼 있으면 slurp 후 호출 객체를 close해요.
핸들이 이진 모드일 때 이 메서드를 호출하면 X::IO::BinaryMode 예외가 던져져요.
my $fh = 'path/to/file'.IO.open;
$fh.split: '♥', :close; # Returns file content split on ♥
method spurt
multi method spurt(IO::Handle:D: Blob $data, :$close = False)
multi method spurt(IO::Handle:D: Cool $data, :$close = False)
$data를 전부 파일핸들에 쓰고, $close가 True면 끝나면 닫아요. Cool인 $data에 대해서는 핸들이 사용하도록 설정된 인코딩(IO::Handle.open 또는 IO::Handle.encoding으로 설정된)을 사용해요.
핸들이 이진 모드일 때 Cool을 spurt하거나, 핸들이 이진 모드가 아닐 때 Blob을 spurt하는 동작은 정의되지 않아요.
method print
multi method print(**@text --> True)
multi method print(Junction:D --> True)
주어진 @text를 핸들에 쓰고, Str이 아닌 객체는 해당 객체의 .Str 메서드를 호출해 Str로 강제 변환해요. Junction 인자는 autothread되고, 출력 문자열의 순서는 보장되지 않아요. 바이트를 쓰려면 write를 참고하세요.
핸들이 이진 모드일 때 이 메서드를 호출하면 X::IO::BinaryMode 예외가 던져져요.
my $fh = 'path/to/file'.IO.open: :w;
$fh.print: 'some text';
$fh.close;
method print-nl
method print-nl(IO::Handle:D: --> True)
$.nl-out 속성의 값을 핸들에 써요. 이 속성은 기본적으로 이지만, 서로 다른 플랫폼과 환경에서 따르는 규칙에 대해서는 newline 페이지를 참고하세요.
핸들이 이진 모드일 때 이 메서드를 호출하면 X::IO::BinaryMode 예외가 던져져요.
my $fh = 'path/to/file'.IO.open: :w, :nl-out("\r\n");
$fh.print: "some text";
$fh.print-nl; # prints \r\n
$fh.close;
method printf
multi method printf(IO::Handle:D: Cool $format, *@args)
주어진 형식과 인자에 기반해 문자열을 서식화하고 결과를 파일핸들에 .print해요. 허용되는 형식 지시자에 대한 자세한 내용은 sprintf를 참고하세요.
핸들이 이진 모드일 때 이 메서드를 호출하면 X::IO::BinaryMode 예외가 던져져요.
my $fh = open 'path/to/file', :w;
$fh.printf: "The value is %d\n", 32;
$fh.close;
method out-buffer
method out-buffer(--> Int:D) is rw
출력 버퍼링을 제어하고 open의 인자로 설정할 수 있어요. 사용할 버퍼 크기로 int를 받아요(0도 허용). Bool도 받을 수 있어요. True는 기본값인, 구현 정의 버퍼 크기를 사용하라는 뜻이고, False는 버퍼링을 비활성화하라는 뜻(버퍼 크기 0과 동일)이에요.
마지막으로 Nil을 받아 TTY 기반 버퍼링 제어를 활성화할 수 있어요. 핸들이 TTY면 버퍼링이 비활성화되고, 아니면 기본값인 구현 정의 버퍼 크기가 사용돼요.
현재 버퍼에 있는 데이터를 쓰려면 flush를 참고하세요. 버퍼 크기를 바꾸면 파일핸들이 flush돼요.
given 'foo'.IO.open: :w, :1000out-buffer {
.say: 'Hello world!'; # buffered
.out-buffer = 42; # buffer resized; previous print flushed
.say: 'And goodbye';
.close; # closing the handle flushes the buffer
}
method put
multi method put(**@text --> True)
multi method put(Junction:D --> True)
주어진 @text를 핸들에 쓰고, Str이 아닌 객체는 .Str 메서드를 호출해 Str로 강제 변환하며, 끝에 .nl-out의 값을 덧붙여요. Junction 인자는 autothread되고 출력 문자열의 순서는 보장되지 않아요.
핸들이 이진 모드일 때 이 메서드를 호출하면 X::IO::BinaryMode 예외가 던져져요.
my $fh = 'path/to/file'.IO.open: :w;
$fh.put: 'some text';
$fh.close;
method say
multi method say(IO::Handle:D: **@text --> True)
이 메서드는 put과 동일한데, 인자를 .Str 대신 .gist를 호출해 문자열화한다는 점만 달라요.
핸들이 이진 모드일 때 이 메서드를 호출하면 X::IO::BinaryMode 예외가 던져져요.
my $fh = open 'path/to/file', :w;
$fh.say(Complex.new(3, 4)); # OUTPUT: «3+4i»
$fh.close;
method read
method read(IO::Handle:D: Int(Cool:D) $bytes = 65536 --> Buf:D)
이진 읽기. 파일핸들에서 최대 $bytes 바이트를 읽어 반환해요. $bytes는 구현별 값을 기본값으로 가지며(Rakudo에서는 $*DEFAULT-READ-ELEMS 값, 기본값은 65536), 이 메서드는 핸들이 이진 모드가 아니어도 호출할 수 있어요.
(my $file = 'foo'.IO).spurt: 'I ♥ Raku';
given $file.open {
say .read: 6; # OUTPUT: «Buf[uint8]:0x<49 20 e2 99 a5 20>»
.close;
}
method readchars
method readchars(IO::Handle:D: Int(Cool:D) $chars = 65536 --> Str:D)
문자 읽기. 파일핸들에서 최대 $chars 문자(그래핌)를 읽어 반환해요. $chars는 구현별 값을 기본값으로 가지며(Rakudo에서는 $*DEFAULT-READ-ELEMS 값, 기본값은 65536), 핸들이 이진 모드일 때 이 메서드를 호출하면 X::IO::BinaryMode 예외가 던져져요.
(my $file = 'foo'.IO).spurt: 'I ♥ Raku';
given $file.open {
say .readchars: 5; # OUTPUT: «I ♥ R»
.close;
}
참고: .readchars 사용 후 .tell을 호출하면 유니코드 처리의 결과로 최대 3바이트까지 어긋날 수 있어요.
method write
method write(IO::Handle:D: Blob:D $buf --> True)
$buf를 파일핸들에 써요. 이 메서드는 핸들이 이진 모드가 아니어도 호출할 수 있어요.
method seek
method seek(IO::Handle:D: Int:D $offset, SeekType:D $whence --> True)
파일 포인터(즉 이후의 읽기·쓰기 연산이 시작될 위치)를 $whence로 지정된 위치에서 $offset만큼 떨어진 바이트 위치로 이동해요. $whence는 다음 중 하나일 수 있어요.
SeekFromBeginning: 파일의 시작.SeekFromCurrent: 파일의 현재 위치.SeekFromEnd: 파일의 끝. 파일 끝 앞의 위치로 가고 싶다면 음의 offset을 지정해야 한다는 점을 기억하세요.
method tell
method tell(IO::Handle:D: --> Int:D)
파일 포인터의 현재 위치를 바이트 단위로 반환해요.
method slurp-rest
multi method slurp-rest(IO::Handle:D: :$bin! --> Buf)
multi method slurp-rest(IO::Handle:D: :$enc --> Str)
DEPRECATION 알림: 이 메서드는 6.d 버전에서 deprecated됐어요. 새 코드에서는 쓰지 말고, 대신 .slurp 메서드를 쓰세요.
현재 파일 위치(이전 읽기나 seek로 설정됐을 수 있음)부터 파일의 나머지 내용을 반환해요. :bin 부사가 제공되면 Buf가 반환되고, 그렇지 않으면 선택적 인코딩 :enc가 적용된 Str이 반환돼요.
method slurp
method slurp(IO::Handle:D: :$close, :$bin)
현재 파일 포인터부터 끝까지의 내용 전체를 반환해요. 호출 객체가 이진 모드이거나 $bin이 True로 설정돼 있으면 Buf를 반환하고, 그렇지 않으면 호출 객체의 현재 .encoding으로 내용을 디코딩해 Str을 반환해요.
:$close가 True로 설정돼 있으면 읽기를 끝낼 때 핸들을 닫아요.
참고: Rakudo에서 이 메서드는 2017.04 릴리스에 도입됐고, $bin 인자는 2017.10에 추가됐어요.
method Supply
multi method Supply(IO::Handle:D: :$size = 65536)
핸들의 내용을 청크 단위로 방출하는 Supply를 반환해요. 청크는 핸들이 이진 모드면 Buf, 아니면 IO::Handle.encoding과 같은 인코딩으로 디코딩된 Str이에요.
청크 크기는 선택적 :size 명명 매개변수로 정해지며, 이진 모드에서는 65536 바이트, 비이진 모드에서는 65536 문자예요.
"foo".IO.open(:bin).Supply(:size<10>).tap: *.raku.say;
# OUTPUT:
# Buf[uint8].new(73,32,226,153,165,32,80,101,114,108)
# Buf[uint8].new(32,54,33,10)
"foo".IO.open.Supply(:size<10>).tap: *.raku.say;
# OUTPUT:
# "I ♥ Perl"
# " 6!\n"
method path
method path(IO::Handle:D:)
파일에 대해 연 핸들에서는 그 파일을 나타내는 IO::Path를 반환해요. 표준 입출력 핸들 $*IN, $*OUT, $*ERR에 대해서는 IO::Special 객체를 반환해요.
method IO
method IO(IO::Handle:D:)
.path의 별칭이에요.
method Str
.path의 값을 Str로 강제 변환해 반환해요.
say "foo".IO.open.Str; # OUTPUT: «foo»
routine close
method close(IO::Handle:D: --> Bool:D)
multi close(IO::Handle $fh)
열린 파일핸들을 닫고 성공하면 True를 반환해요. 파일핸들이 이미 닫혀 있어도 오류는 던져지지 않지만, 표준 파일핸들 중 하나(기본적으로 $*IN, $*OUT, $*ERR: native-descriptor가 2 이하인 핸들)를 닫으면 다시 열 수 없게 돼요.
given "foo/bar".IO.open(:w) {
.spurt: "I ♥ Raku!";
.close;
}
핸들을 닫을 때 LEAVE phaser를 쓰는 것이 흔한 관용구인데, 블록을 어떻게 떠나든 핸들이 닫히도록 보장해줘요.
do {
my $fh = open "path-to-file";
LEAVE close $fh;
# ... do stuff with the file
}
sub do-stuff-with-the-file (IO $path-to-file) {
my $fh = $path-to-file.open;
# stick a `try` on it, since this will get run even when the sub is
# called with wrong arguments, in which case the `$fh` will be an `Any`
LEAVE try close $fh;
# ... do stuff with the file
}
참고: 일부 다른 언어와 달리 Raku는 참조 카운팅을 사용하지 않으므로 파일핸들은 범위를 벗어나도 닫히지 않아요. 가비지 컬렉션되면 닫히긴 하지만 가비지 컬렉션이 실행된다는 보장이 없어요. 즉 데이터 손실을 피하려면 쓰기용으로 연 핸들에는 명시적 close를 반드시 해야 하고, 읽기용으로 연 핸들에도 명시적 close가 권장돼요. 그래야 프로그램이 한 번에 너무 많은 파일을 열어 이후 open 호출에서 예외가 발생하는 일을 피할 수 있어요.
여러 메서드가 :close 인자를 제공해 메서드가 호출한 연산이 끝나면 핸들을 닫게 할 수 있어요. 더 단순한 대안으로 IO::Path 타입은 파일핸들을 직접 다루지 않고 파일로 작업할 수 있는 많은 읽기·쓰기 메서드를 제공해요.
method flush
method flush(IO::Handle:D: --> True)
핸들을 flush해서 버퍼링된 데이터를 모두 써요. 성공하면 True를 반환하고, 그렇지 않으면 X::IO::Flush로 fail해요.
given "foo".IO.open: :w {
LEAVE .close;
.print: 'something';
'foo'.IO.slurp.say; # (if the data got buffered) OUTPUT: «»
.flush; # flush the handle
'foo'.IO.slurp.say; # OUTPUT: «something»
}
method native-descriptor
method native-descriptor(IO::Handle:D:)
운영체제가 "파일 디스크립터"로 이해하는 값을 반환해요. fcntl이나 ioctl처럼 파일 디스크립터를 인자로 요구하는 네이티브 함수에 넘기기에 적합해요.
method nl-in
method nl-in(--> Str:D) is rw
.new나 open으로 설정할 수 있는 속성 중 하나예요. 기본값은 ["\x0A", "\r\n"]. 이 핸들에 대한 입력 줄 끝(들)을 지정하는 Str 또는 Str의 Array를 받아요. .chomp 속성이 True로 설정돼 있으면 get과 lines 같은 chomp하는 루틴에서 이 끝들을 제거해요.
with 'test'.IO {
.spurt: '1foo2bar3foo'; # write some data into our test file
my $fh will leave {.close} = .open; # can also set .nl-in via .open arg
$fh.nl-in = [<foo bar>]; # set two possible line endings to use;
$fh.lines.say; # OUTPUT: ("1", "2", "3").Seq
}
method nl-out
has Str:D $.nl-out is rw = "\n";
.new나 open으로 설정할 수 있는 속성 중 하나예요. 기본값은 "\n". .put과 .say 메서드가 사용할 이 핸들의 출력 줄 끝을 지정하는 Str을 받아요.
with 'test'.IO {
given .open: :w {
.put: 42;
.nl-out = 'foo';
.put: 42;
.close;
}
.slurp.raku.say; # OUTPUT: «"42\n42foo"»
}
method opened
method opened(IO::Handle:D: --> Bool:D)
핸들이 열려 있으면 True, 아니면 False를 반환해요.
method t
method t(IO::Handle:D: --> Bool:D)
핸들이 TTY로 열려 있으면 True, 아니면 False를 반환해요.
사용자 정의 핸들 만들기 (Creating Custom Handles)
6.d 언어부터(Rakudo 컴파일러 2018.08 릴리스에 초기 구현), 사용자 정의 IO::Handle 객체 생성을 단순화하는 몇몇 헬퍼 메서드를 사용할 수 있어요. 서브클래스에서 이런 메서드들을 구현하기만 하면 관련 기능 전체에 영향을 주죠. 핸들이 텍스트 읽기·쓰기 메서드와 함께 작동하길 원하고 표준 .open 메서드를 사용하지 않는다면, 사용자 정의 핸들에서 .encoding 메서드를 호출해 디코더/인코더를 제대로 설정해야 해요.
class IO::URL is IO::Handle {
has $.URL is required;
has Buf $!content;
submethod TWEAK {
use WWW; # ecosystem module that will let us `get` a web page
use DOM::Tiny; # ecosystem module that will parse out all text from HTML
$!content := Buf.new: DOM::Tiny.parse(get $!URL).all-text(:trim).encode;
self.encoding: 'utf8'; # set up encoder/decoder
}
method open(|) { self } # block out some IO::Handle methods
method close(|) { self } # that work with normal low-level file
method opened { ! self.EOF } # handles, since we don't. This isn't
method lock(| --> True) { } # necessary, but will make our handle
method unlock( --> True) { } # be more well-behaved if someone
# actually calls one of these methods. There are more of these you
# can handle, such as .tell, .seek, .flush, .native-descriptor, etc.
method WRITE(|) {
# For this handle we'll just die on write. If yours can handle writes.
# The data to write will be given as a Blob positional argument.
die "Cannot write into IO::URL";
}
method READ(\bytes) {
# We splice off the requested number of bytes from the head of
# our content Buf. The handle's decoder will handle decoding them
# automatically, if textual read methods were called on the handle.
$!content.splice: 0, bytes
}
method EOF {
# For "end of file", we'll simply report whether we still have
# any bytes of the website we fetched on creation.
not $!content
}
}
my $fh := IO::URL.new: :URL<raku.org>;
# .slurp and print all the content from the website. We can use all other
# read methods, such as .lines, or .get, or .readchars. All of them work
# correctly, even though we only defined .READ and .EOF
$fh.slurp.say;
method WRITE
method WRITE(IO::Handle:D: Blob:D \data --> Bool:D)
핸들에 쓰기 연산이 수행될 때마다 호출돼요. 텍스트 쓰기 메서드가 호출됐어도 항상 데이터를 Blob으로 받아요.
class IO::Store is IO::Handle {
has @.lines = [];
submethod TWEAK {
self.encoding: 'utf8'; # set up encoder/decoder
}
method WRITE(IO::Handle:D: Blob:D \data --> Bool:D) {
@!lines.push: data.decode();
True;
}
method gist() {
return @!lines.join("\n" );
}
}
my $store = IO::Store.new();
my $output = $PROCESS::OUT;
$PROCESS::OUT = $store;
.say for <one two three>;
$PROCESS::OUT = $output;
say $store.lines(); # OUTPUT: «[one two three]»
이 예시에서 우리는 파일핸들에 쓰여진 모든 것을 배열에 저장하는 간단한 WRITE 리다이렉션을 만들고 있어요. 먼저 표준 출력을 $output에 저장하고, 그다음 print되거나 say를 통해 말해진 모든 것이 정의된 IO::Store 클래스에 저장돼요. 이 클래스에서 두 가지를 고려해야 해요. 기본적으로 IO::Handle은 이진 모드이므로 텍스트와 함께 작동하려면 객체를 TWEAK해야 해요. 둘째, WRITE 연산은 성공하면 True를 반환해야 해요. 그렇지 않으면 실패해요.
method READ
method READ(IO::Handle:D: Int:D \bytes --> Buf:D)
핸들에 읽기 연산이 수행될 때마다 호출돼요. 읽을 요청 바이트 수를 받아요. 그 바이트들로 이루어진 Buf를 반환해요. 이는 디코더 버퍼를 채우거나 읽기 메서드에서 직접 반환될 수 있어요. 결과는 요청한 바이트 수보다 적거나, 아예 0바이트여도 허용돼요.
자신만의 .READ를 제공한다면, 모든 기능이 올바르게 동작하도록 자신만의 .EOF도 제공할 필요가 아주 높아요.
컴파일러는 읽기 연산 중 .READ 호출을 해야 할지 판단하기 위해 .EOF 메서드를 몇 번이라도 호출할 수 있어요. 읽기 연산을 충족시키는 데 필요한 것보다 더 많은 바이트가 .READ에 요청될 수 있고, 그 경우 추가 데이터는 IO::Handle이나 그것이 사용하는 디코더가 버퍼링해, 반드시 또 .READ를 호출하지 않고도 이후 읽기 연산을 충족시킬 수 있어요.
class IO::Store is IO::Handle {
has @.lines = [];
submethod TWEAK {
self.encoding: 'utf8'; # set up encoder/decoder
}
method WRITE(IO::Handle:D: Blob:D \data --> Bool:D) {
@!lines.push: data;
True;
}
method whole() {
my Buf $everything = Buf.new();
for @!lines -> $b {
$everything ~= $b;
}
return $everything;
}
method READ(IO::Handle:D: Int:D \bytes --> Buf:D) {
my Buf $everything := self.whole();
return $everything;
}
method EOF {
my $everything = self.whole();
!$everything;
}
}
my $store := IO::Store.new();
$store.print( $_ ) for <one two three>;
say $store.read(3).decode; # OUTPUT: «one»
say $store.read(3).decode; # OUTPUT: «two»
이 경우 우리는 READ와 EOF 두 메서드와, 모든 줄을 배열의 요소에 저장하는 WRITE를 프로그래밍했어요. read 메서드는 실제로 READ를 호출해 3바이트를 반환하는데, 이는 처음 두 요소에 있는 세 문자에 대응해요. 커서(cursor)를 처리하는 것은 IO::Handle 베이스 클래스라는 점을 기억하세요. READ는 객체의 전체 내용에 대한 핸들을 제공할 뿐이니까요. 베이스 클래스는 한 번에 1024 * 1024 바이트를 READ해요. 객체가 그보다 더 큰 바이트 양을 담도록 계획되어 있다면 내부 커서를 직접 처리해야 해요. 그래서 이 예시에서는 실제로 bytes 인자를 사용하지 않아요.
method EOF
method EOF(IO::Handle:D: --> Bool:D)
핸들의 데이터 소스에 대해 "파일 끝"에 도달했는지, 즉 .READ 메서드를 호출해 더 이상 데이터를 얻을 수 없는지 여부를 나타내요. 이는 eof 메서드와 다르다는 점을 명심하세요. eof는 .EOF가 True를 반환하고 핸들이 사용한 디코더 버퍼가 (있다면) 모두 비어 있을 때만 True를 반환해요. 예시 구현은 .READ를 참고하세요.
관련 롤과 클래스 (Related roles and classes)
관련 롤 IO와 관련 클래스 IO::Path도 함께 보세요.