Go 파일 입출력 기본 사용법 (os, ioutil, bufio) – 업무에 사용하는 Go 언어 응용편 1

Go 파일 입출력
Go 파일 입출력

Go 언어를 막 배운 상황에서 실제 업무에 활용하려 할 때 가장 먼저 부딪히는 문제 중 하나가 “파일 입출력”입니다. 로그를 저장하거나 설정 파일을 읽는 등 실무에서 파일을 다루는 일은 매우 흔하기 때문에, os, ioutil, bufio 패키지를 이용한 Go 파일 입출력에 대한 이해는 꼭 필요합니다.

이 글에서는 파일을 읽고 쓰는 기본적인 방법부터, 성능과 편의성을 고려한 다양한 접근 방법까지 하나하나 설명드리겠습니다. 응용편인 만큼 단순한 이론 설명에 그치지 않고, 실무에서 자주 마주치는 상황을 예시로 들어 이해를 돕겠습니다.

필자는 프로그래밍 언어에 대한 기본적인 문법을 학습했다면 가장 먼저 해보면 좋은 것이 파일 입출력이라고 생각한다.
실제로 로그 저장, 설정 파일 로드, 캐시 저장 등 업무에서 자주 사용하는 기능이기 때문입니다.

Go 파일 입출력 열기와 닫기 – os 패키지

Go에서 파일을 열기 위해 가장 먼저 사용하는 패키지는 os입니다. 이 패키지를 통해 파일을 열고 닫고, 생성하거나 삭제할 수 있습니다.

Go
file, err := os.Open("example.txt")
if err != nil {
    log.Fatal(err)
}
defer file.Close()
  • os.Open()읽기 전용으로 파일을 엽니다.
  • 오류가 발생하면 err를 통해 확인할 수 있습니다.
  • defer file.Close()를 통해 함수가 종료될 때 자동으로 파일이 닫히도록 합니다.

파일을 새로 만들거나 기존 파일을 덮어쓰기 위해서는 os.Create()를 사용합니다.

Go
file, err := os.Create("newfile.txt")
if err != nil {
    log.Fatal(err)
}
defer file.Close()
  • os.Create()는 파일이 없으면 생성하고, 있으면 내용을 초기화합니다.
  • 실무에서 임시 파일 생성 시에도 자주 사용됩니다.

파일에 문자열 쓰기 – Write와 WriteString

파일 객체는 Write 메서드를 통해 데이터를 쓸 수 있습니다. 이 기능은 로그 기록, 데이터 저장 등에서 자주 사용됩니다.

Go
file.Write([]byte("Hello, world!\n"))
file.WriteString("또 다른 줄입니다.\n")
  • Write()는 바이트 슬라이스를 받습니다.
  • WriteString()은 문자열을 바로 쓸 수 있어 더 편리합니다.

로그 파일을 남기는 상황에서 이 기능을 자주 활용하게 됩니다. 특히, WriteString()은 문자열 처리 시 간결하고 직관적이어서 많이 사용됩니다.


간단한 파일 읽기 – ioutil.ReadFile

파일을 한 번에 통째로 읽고 싶을 때는 ioutil.ReadFile()을 사용하면 매우 간단합니다. 예를 들어 설정 파일(config.json)을 통째로 메모리에 불러올 때 유용합니다.

Go
data, err := ioutil.ReadFile("example.txt")
if err != nil {
    log.Fatal(err)
}
fmt.Println(string(data))
  • 이 방식은 파일의 크기가 작을 때 매우 유용합니다.
  • JSON, CSV 등의 설정 파일을 읽어오는 경우에 적합합니다.
⚠️ 참고
Go 1.16 이후로는 ioutil이 deprecated 처리되어 os.ReadFile, os.WriteFile, io를 사용하는 것이 권장된다.
필자는 ioutil에 대한 설명을 아예 하지 않으려고 했으나 여전히 많은 예제에서 ioutil이 사용되고 있으므로 이해는 필수라고 생각한다.
만약 좀 더 자세한 내용이 궁금하다면 다음 링크를 통해 학습해보는 것을 권장한다.
Go ioutil 패키지는 왜 사라졌을까?

한 줄씩 읽기 – bufio.NewScanner

대부분의 실무에서는 로그 파일이나 텍스트 파일을 한 줄씩 읽어 처리하는 경우가 많습니다. 이럴 때는 bufio.Scanner를 사용합니다. 로그 분석, 줄 단위 처리 로직에서 특히 유용합니다.

Go
file, err := os.Open("example.txt")
if err != nil {
    log.Fatal(err)
}
defer file.Close()

scanner := bufio.NewScanner(file)
for scanner.Scan() {
    line := scanner.Text()
    fmt.Println(line)
}

if err := scanner.Err(); err != nil {
    log.Fatal(err)
}
  • bufio.NewScanner()는 파일을 읽을 준비를 합니다.
  • scanner.Scan()은 다음 줄을 읽고 true/false를 반환합니다.
  • scanner.Text()는 현재 줄의 문자열을 반환합니다.

로그 파일을 읽고 분석하는 시스템에서 자주 사용되며, 실시간 처리 시스템에서도 유용하게 활용됩니다.


버퍼를 이용한 효율적인 쓰기 – bufio.Writer

쓰기 작업을 반복적으로 할 경우, bufio.Writer를 통해 버퍼를 이용하면 성능 향상에 도움이 됩니다. 특히 많은 로그를 기록하거나 대량의 데이터를 저장하는 경우 성능 차이가 큽니다.

Go
file, err := os.Create("output.txt")
if err != nil {
    log.Fatal(err)
}
defer file.Close()

writer := bufio.NewWriter(file)

writer.WriteString("첫 번째 줄입니다.\n")
writer.WriteString("두 번째 줄입니다.\n")
writer.Flush()
  • bufio.NewWriter()는 내부 버퍼를 사용하는 writer를 생성합니다.
  • Flush()를 호출해야 실제 파일에 데이터가 기록됩니다. 자동으로 flush되지 않기 때문에 주의가 필요합니다.

특히 웹 서버나 데이터 처리 루틴에서 쓰기 버퍼는 성능 향상에 결정적인 역할을 합니다.


파일 존재 여부 확인하기

실무에서는 파일이 존재하는지를 먼저 체크해야 하는 경우가 많습니다. 예를 들어 설정 파일을 읽기 전에 그 파일이 있는지를 먼저 확인해야 합니다. 이때는 os.Stat()을 사용합니다.

Go
if _, err := os.Stat("config.json"); os.IsNotExist(err) {
    fmt.Println("파일이 존재하지 않습니다.")
} else {
    fmt.Println("파일이 존재합니다.")
}
  • 존재하지 않는 파일을 열 경우 에러가 발생하므로, 사전 체크는 안정적인 프로그램 구현에 필수입니다.
  • 백업 파일이나 로그 파일 존재 여부를 체크할 때도 자주 사용됩니다.

defer는 반드시 사용

파일을 열고 난 후 닫지 않으면, 시스템 자원이 낭비되거나 심각한 문제가 발생할 수 있습니다. 따라서 defer file.Close()는 반드시 사용하는 습관을 들이시기 바랍니다. 수십 개의 파일을 동시에 열어야 하는 상황이라면 더더욱 중요합니다.


파일 읽기와 쓰기를 함께 하기

실무에서는 파일을 읽고 수정한 뒤 다시 저장하는 경우도 많습니다. 이때는 os.OpenFile()을 활용하여 읽기와 쓰기를 동시에 수행할 수 있습니다.

Go
file, err := os.OpenFile("log.txt", os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)
if err != nil {
    log.Fatal(err)
}
defer file.Close()
  • os.O_RDWR: 읽기 및 쓰기
  • os.O_CREATE: 파일이 없으면 생성
  • os.O_APPEND: 기존 내용 뒤에 덧붙이기

복잡한 파일 처리가 필요한 프로젝트에서 매우 유용하게 사용됩니다.


이번 글에서는 Go 파일 입출력을 다루기 위해 꼭 알아야 할 패키지들과 그 사용법을 살펴보았습니다.

  • os로 파일을 열고 닫고 생성하고 삭제할 수 있습니다.
  • ioutil.ReadFile로 간단하게 전체 파일을 읽을 수 있습니다.
  • bufio.Scanner로 파일을 한 줄씩 읽을 수 있고, bufio.Writer로 버퍼를 통한 효율적인 쓰기가 가능합니다.
  • os.Stat()으로 파일의 존재 여부를 체크할 수 있습니다.
  • os.OpenFile()을 통해 읽기와 쓰기를 동시에 할 수 있습니다.

Go 언어에서 파일을 다루는 방식은 비교적 직관적이며, 함수와 메서드 이름도 명확합니다. 초보자도 개념만 정확히 이해하면 실무에서 바로 활용할 수 있습니다. 이번 기회에 Go 파일 입출력의 기본기를 확실히 익히시고, 다음 단계인 JSON 파일 처리나 템플릿 저장 기능으로 확장해 보시길 바랍니다.