앞서 비트스트링을 다루기 위해 제공되는 <<>> 특수 폼에서 봤듯이, Elixir는 바이너리 데이터를 다루는 우아한 문법을 제공해요.
바이너리 타입은 비트스트링 타입을 특수화한 것이에요. 비트스트링은 길이에 제한이 없지만(비트 개수가 얼마든 될 수 있어요), 바이너리는 비트 수가 8로 나누어떨어지는 경우를 말해요. 즉, 바이너리를 다룰 때는 흔히 바이트(8비트) 단위로 생각해요. 바이트 하나는 0부터 255까지의 정수를 나타낼 수 있어요. 바이트 값은 16진수인 0x00 - 0xFF로 다루는 경우가 많아요.
바이너리 리터럴은 비트스트링 특수 폼 <<>>로 정의해요. 바이너리 리터럴을 정의할 때는 정수 리터럴과 문자열 리터럴을 사용할 수 있어요. 255보다 큰 정수 값은 오버플로가 발생해서 정수의 마지막 8비트만 사용돼요. 기본적으로 값에는 ::binary 수식어가 적용돼요. <>/2 연산자로 바이너리를 이어 붙일 수 있어요.
<<255>> == <<0xFF>>
# Overflowing bits are truncated
<<256>> == <<0>>
<<2, 4, 6, 8, 10, 12, 14, 16>> == <<0x02, 0x04, 0x06, 0x08, 0x0A, 0x0C, 0x0E, 0x10>>
_널 바이트_는 <<0>>의 다른 이름이에요.
패턴 매칭은 바이너리에도 적용돼요. 리스트에서 그러듯 바이너리 데이터의 일부분에 대해서도 패턴 매칭을 할 수 있어요.
# Ignore the first 8 bytes, match and bind the remaining to `body`
<<_::binary-size(8), body::binary>>
다른 패턴 매칭과 마찬가지로, 여러 함수 절 중에서 선택할 때 함수 시그니처에서도 이걸 사용할 수 있어요.
사용자가 서버에 파일을 업로드해 다른 사용자와 공유할 수 있도록 하는 프로젝트를 진행하고 있어요. 업로드한 파일이 미디어 타입과 일치하는지 확인하는 함수를 작성하는 일을 맡았어요. 자료를 좀 찾아보니, 파일의 처음 몇 바이트는 대개 그 파일 형식만의 고유한 값이라서 일종의 서명 역할을 한다는 걸 알게 됐어요.
다음 표를 참고해요:
| 파일 형식 | 일반적인 확장자 | 미디어 타입 | 바이너리 '서명' |
|---|---|---|---|
| ELF | "exe" |
"application/octet-stream" |
0x7F, 0x45, 0x4C, 0x46 |
| BMP | "bmp" |
"image/bmp" |
0x42, 0x4D |
| PNG | "png" |
"image/png" |
0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A |
| JPG | "jpg" |
"image/jpg" |
0xFF, 0xD8, 0xFF |
| GIF | "gif" |
"image/gif" |
0x47, 0x49, 0x46 |
type_from_extension/1 함수를 구현해요. 이 함수는 파일 확장자(문자열)를 받아서 미디어 타입(문자열)을 반환하고, 확장자가 예상하는 값과 일치하지 않으면 nil을 반환해요.
FileSniffer.type_from_extension("exe")
# => "application/octet-stream"
FileSniffer.type_from_extension("txt")
# => nil
type_from_binary/1 함수를 구현해요. 이 함수는 파일(바이너리)을 받아서 미디어 타입(문자열)을 반환하고, 파일 서명이 예상하는 값과 일치하지 않으면 nil을 반환해요.
file = File.read!("application.exe")
FileSniffer.type_from_binary(file)
# => "application/octet-stream"
file = File.read!("example.txt")
FileSniffer.type_from_binary(file)
# => nil
파일을 바이너리로 읽는 부분은 신경 쓰지 않아요. 그건 이미 처리되어 있고, 테스트가 인자로 넘겨준다고 가정해요.
verify/2 함수를 구현해요. 이 함수는 파일(바이너리)과 확장자(문자열)를 받아서 :ok 또는 :error 튜플을 반환해요.
file = File.read!("application.exe")
FileSniffer.verify(file, "exe")
# => {:ok, "application/octet-stream"}
FileSniffer.verify(file, "png")
# => {:error, "Warning, file format and file extension do not match."}
Exercism에 가입하고 Elixir 트랙을 개념 58개연습 문제 168개, 그리고 실제 사람의 멘토링과 함께 배우고 익혀 보세요. 모두 무료예요.