Quick Flow
data, err := os.ReadFile("config.json")
if err != nil {
return err
}
if err := os.WriteFile("out.txt", data, 0o644); err != nil {
return err
}Go 파일 코드는 작은 파일 일괄 처리, 스트림 처리, 빌드 타임 내장을 먼저 구분하면 선택이 쉬워집니다.
기본 흐름
작은 파일은 ReadFile과 WriteFile로 충분하다
data, err := os.ReadFile("users.json")
if err != nil {
return err
}파일 전체를 메모리에 올려도 되는 설정 파일, 작은 JSON, 테스트 fixture는 os.ReadFile이 가장 단순합니다. 반대로 큰 로그나 업로드 파일처럼 크기가 커질 수 있으면 스트림으로 읽는 편이 안전합니다.
파일 핸들은 열었으면 닫는다
f, err := os.Open("input.txt")
if err != nil {
return err
}
defer f.Close()os.Open은 파일 핸들을 돌려줍니다. 함수가 끝날 때 닫히도록 defer f.Close()를 붙이는 흐름이 기본입니다.
io.Copy도 source나 destination을 자동으로 닫지 않습니다. 누가 Close를 호출할지는 열린 쪽의 책임입니다. 출력 파일처럼 Close에서 flush 오류가 날 수 있는 자원은 close 오류까지 반환해야 하므로, 짧은 읽기 예제의 defer f.Close()를 쓰기 경로에 그대로 복사하면 저장 실패를 놓칠 수 있습니다.
스트림 복사는 io.Copy로 읽는다
if _, err := io.Copy(dst, src); err != nil {
return err
}io.Reader에서 io.Writer로 옮기는 흐름은 io.Copy가 표준 표면입니다. 파일, HTTP body, 압축 스트림처럼 구체 타입이 달라도 reader/writer 인터페이스로 묶을 수 있습니다.
줄 단위 입력은 bufio.Scanner가 읽기 쉽다
scanner := bufio.NewScanner(f)
for scanner.Scan() {
line := scanner.Text()
_ = line
}
if err := scanner.Err(); err != nil {
return err
}Scanner는 줄 단위 텍스트를 다루기 좋습니다. 아주 긴 라인이나 바이너리 데이터는 bufio.Reader나 직접 버퍼링을 검토합니다.
기본 token 한계를 넘을 수 있는 입력은 첫 Scan 전에 최대 크기를 정합니다. 무제한으로 키우는 대신 프로토콜의 한 줄 최대 크기를 정해야 합니다.
scanner := bufio.NewScanner(f)
scanner.Buffer(make([]byte, 0, 64*1024), 1024*1024)설정이나 상태 파일을 교체할 때 os.WriteFile은 간단하지만 원자적 교체를 보장하지는 않습니다. 중간 파일을 같은 디렉터리에 쓰고 Close 오류를 확인한 뒤 rename하는 흐름은 별도 카드나 프로젝트 저장 정책으로 둡니다.
내장 파일
문자열이나 바이트로 파일을 내장한다
import _ "embed"
//go:embed static/index.html
var indexHTML string//go:embed는 빌드 시점의 파일을 바이너리에 포함합니다. 런타임에 파일을 따로 배포하지 않아도 되는 템플릿, 작은 정적 파일, 기본 설정을 넣을 때 유용합니다.
여러 파일은 embed.FS로 다룬다
import "embed"
//go:embed templates/*
var templates embed.FS
data, err := templates.ReadFile("templates/index.html")
if err != nil {
return err
}embed.FS는 내장 파일을 파일 시스템처럼 읽는 표면입니다. 웹 서버에서 정적 파일을 서빙하거나 템플릿 묶음을 다룰 때 자주 씁니다.
embed 경로는 빌드 시점에 해석됩니다. 파일이 패턴에 맞지 않거나 build context 밖에 있으면 컴파일 단계에서 실패하며, 사용자 입력으로 embed 경로를 바꾸는 기능은 만들 수 없습니다. 사용자 입력 파일 경로는 filepath.Clean만으로 신뢰하지 말고 허용 root 밖으로 나가지 않는지 별도로 검사합니다.
선택 기준
| 상황 | 먼저 떠올릴 선택 |
|---|---|
| 작은 파일 전체 읽기 | os.ReadFile |
| 작은 파일 전체 쓰기 | os.WriteFile |
| 큰 데이터 복사 | io.Copy |
| 줄 단위 텍스트 | bufio.Scanner |
| 빌드에 파일 포함 | //go:embed |
| 여러 내장 파일 | embed.FS |
주의할 점
embed는 런타임 파일 읽기가 아니라 빌드 타임 내장입니다. 배포 후 파일을 바꿔도 바이너리에 들어간 값은 바뀌지 않습니다. 외부 설정처럼 실행 환경마다 달라져야 하는 값은 embed보다 파일, 환경 변수, 설정 서비스를 따로 두는 편이 맞습니다.
참고 링크
3 sources