Go 언어의 파일 글로빙 이해하기: 유연한 파일 경로 매칭 기법

Go 언어의 파일 글로빙 이해하기: 유연한 파일 경로 매칭 기법

 

파일 시스템을 다루는 작업은 소프트웨어 개발에서 매우 흔한 일입니다.

 

특정 패턴에 맞는 파일을 찾거나, 여러 파일을 일괄적으로 처리해야 할 때가 많은데요.

 

이때 '파일 글로빙'이라는 강력한 메커니즘이 큰 도움이 됩니다.

 

Go 언어에서는 'path/filepath' 패키지를 통해 이러한 파일 글로빙 기능을 기본적으로 제공하며, 개발자들이 특정 패턴과 일치하는 파일이나 디렉터리를 효율적으로 검색할 수 있도록 지원합니다.

 

이 글에서는 파일 글로빙이 무엇인지, 그리고 Go 언어의 'filepath.Glob' 함수를 어떻게 활용하는지 심층적으로 살펴보겠습니다.

 

더 나아가, 발생할 수 있는 일반적인 문제들을 짚어보고, 고급 패턴 매칭을 위한 대안까지 함께 탐구해 보겠습니다.

 

시작하기 전 준비물 및 선수 지식

이 글에서 다루는 개념과 예시들을 더 잘 이해하고 직접 따라 해보기 위해서는 몇 가지 사전 준비와 지식이 필요합니다.

  1. Go 언어 설치: 시스템에 Go 언어가 설치되어 있어야 합니다. 안정적인 최신 버전(예: Go 1.18 이상)을 권장합니다. Go 공식 웹사이트에서 다운로드하여 설치할 수 있습니다.
  2. 기본적인 Go 언어 지식: 함수 선언, 슬라이스(Slice) 사용법, 에러 처리(if err != nil) 등 Go 언어의 기본적인 문법과 개념을 이해하고 있어야 합니다.
  3. 운영체제 파일 시스템 개념: 파일 경로, 디렉터리 구조 등 운영체제의 파일 시스템 동작 방식에 대한 기본적인 이해가 있다면 글로빙 패턴을 구성하는 데 도움이 됩니다.
  4. 터미널/명령 프롬프트 사용 능력: Go 프로그램 실행, 예시 파일 생성 등 대부분의 작업은 터미널 환경에서 이루어집니다.

 

'글로빙'이란 무엇인가요?

'글로빙(Globbing)'은 파일명이나 경로를 특정 패턴에 기반하여 일치시키는 과정을 의미합니다.

 

이 패턴을 정의하기 위해 '와일드카드(Wildcard)' 문자를 사용하는데요.

 

글로빙이 왜 유용한지 이해하려면 이 와일드카드 문자들이 어떻게 동작하는지 아는 것이 중요합니다.

 

대표적인 와일드카드 문자는 다음과 같습니다.

  • '*': 이 문자는 경로 구분자(예: 리눅스/macOS의 '/', 윈도우의 '')를 제외한 모든 문자열의 '임의의 길이'를 매칭합니다. 예를 들어, 'data/*.log'는 'data/access.log', 'data/error_2023.log' 등 'data' 디렉터리 아래의 모든 '.log' 파일을 매칭합니다.
  • '?': 이 문자는 경로 구분자를 제외한 '단일 문자' 하나를 매칭합니다. 예를 들어, 'image_?.png'는 'image_1.png', 'image_A.png'와 같이 'image_' 뒤에 단일 문자가 오는 '.png' 파일을 매칭합니다.
  • '[...]': 대괄호 안에 지정된 문자들 중 '단일 문자' 하나를 매칭합니다. 예를 들어, '[abc].txt'는 'a.txt', 'b.txt', 'c.txt'를 매칭합니다. '[0-9].log'와 같이 범위도 지정할 수 있습니다.
  • '숫자 범위([0-9])': 숫자의 범위를 지정할 수 있습니다. 'log_[0-9][0-9][0-9].txt'는 'log_001.txt', 'log_123.txt'와 같이 3자리 숫자를 가진 로그 파일을 매칭합니다.

이러한 패턴들은 파일 시스템에서 파일을 '유연하고 효율적으로 검색하고 매칭'할 수 있도록 도와줍니다.

 

예를 들어, 여러 개의 로그 파일을 동시에 분석하거나, 특정 유형의 설정 파일을 찾을 때, 혹은 빌드 시스템에서 특정 소스 파일을 자동으로 포함시켜야 할 때 글로빙은 매우 강력한 도구가 됩니다.

 

복잡한 로직 없이도 간결한 패턴만으로 원하는 결과를 얻을 수 있다는 점이 글로빙의 가장 큰 장점입니다.

 

Go 언어에서 'filepath.Glob' 사용하기

Go 언어의 'path/filepath' 패키지에 포함된 'Glob' 함수는 지정된 패턴과 일치하는 모든 파일의 이름을 반환하도록 설계되었습니다.

 

'Glob' 함수의 시그니처는 다음과 같습니다.

func Glob(pattern string) (matches []string, err error)

 

이 함수의 매개변수와 반환 값은 다음과 같은 의미를 가집니다.

  • 'pattern': 파일 또는 디렉터리 이름과 일치시킬 '글로브 패턴' 문자열입니다.
  • 'matches': 매칭된 파일 또는 디렉터리 이름이 담긴 '문자열 슬라이스'입니다.
  • 'err': 패턴 처리 중 발생한 '문제(오류)'를 보고하는 값입니다.

이제 간단한 예시를 통해 'filepath.Glob'의 활용법을 알아보겠습니다.

 

이 예시를 직접 실행해 보려면, 먼저 작업할 디렉터리를 하나 생성하고 그 안에 몇 개의 더미 파일을 만들어 보는 것이 좋습니다.

예시 1: '.go' 확장자를 가진 파일 찾기

현재 디렉터리에서 '.go' 확장자를 가진 모든 파일을 찾아 출력하는 예시입니다.

package main

import (
    "fmt"
    "log"
    "os" // os 패키지를 추가합니다.
    "path/filepath"
)

func main() {
    // 테스트를 위한 더미 파일 생성 (실제 사용 시에는 불필요합니다)
    os.WriteFile("main.go", []byte("package main\n"), 0644)
    os.WriteFile("utils.go", []byte("package utils\n"), 0644)
    os.WriteFile("README.md", []byte("# README\n"), 0644)
    os.WriteFile("temp.txt", []byte("temporary file\n"), 0644)

    pattern := "*.go" // 모든 .go 확장자 파일을 매칭하는 패턴입니다.
    matches, err := filepath.Glob(pattern)
    if err != nil {
        log.Fatalf("패턴 매칭 중 오류 발생: '%v'", err) // 오류 발생 시 프로그램을 종료합니다.
    }

    if len(matches) == 0 {
        fmt.Printf("패턴 '%s'에 일치하는 파일이 없습니다.\n", pattern)
        return
    }

    fmt.Printf("패턴 '%s'에 일치하는 파일:\n", pattern)
    for _, match := range matches {
        fmt.Println(match)
    }

    // 테스트용 더미 파일 삭제 (실제 사용 시에는 불필요합니다)
    os.Remove("main.go")
    os.Remove("utils.go")
    os.Remove("README.md")
    os.Remove("temp.txt")
}

 

위 코드를 'glob_example.go' 파일로 저장하고 실행하면 다음과 유사한 결과가 출력됩니다.

패턴 '*.go'에 일치하는 파일:
main.go
utils.go

 

 

이 예시에서는 'main.go'와 'utils.go'라는 두 개의 Go 파일이 패턴에 매칭되어 출력되는 것을 확인할 수 있습니다.

예시 2: 특정 디렉터리 내의 로그 파일 찾기

이번에는 특정 디렉터리 내에서 특정 패턴을 가진 파일을 찾아보겠습니다.

 

예를 들어, 'logs' 디렉터리 안에 'app_YYYYMMDD.log' 형태의 파일들을 찾는 경우입니다.

 

먼저 다음과 같은 디렉터리 구조를 만들어 줍니다.

your_project/
├── main.go
└── logs/
    ├── app_20231026.log
    ├── app_20231027.log
    ├── system.log
    └── errors.txt

 

'main.go' 파일의 내용은 다음과 같습니다.

package main

import (
    "fmt"
    "log"
    "os"
    "path/filepath"
)

func main() {
    // 테스트를 위한 더미 디렉터리 및 파일 생성 (실제 사용 시에는 불필요합니다)
    os.MkdirAll("logs", 0755)
    os.WriteFile("logs/app_20231026.log", []byte("log content\n"), 0644)
    os.WriteFile("logs/app_20231027.log", []byte("another log\n"), 0644)
    os.WriteFile("logs/system.log", []byte("system log\n"), 0644)
    os.WriteFile("logs/errors.txt", []byte("error text\n"), 0644)

    pattern := "logs/app_????????.log" // 'logs' 디렉터리 안의 'app_YYYYMMDD.log' 형태를 매칭합니다.
    matches, err := filepath.Glob(pattern)
    if err != nil {
        log.Fatalf("패턴 매칭 중 오류 발생: '%v'", err)
    }

    if len(matches) == 0 {
        fmt.Printf("패턴 '%s'에 일치하는 파일이 없습니다.\n", pattern)
        return
    }

    fmt.Printf("패턴 '%s'에 일치하는 파일:\n", pattern)
    for _, match := range matches {
        fmt.Println(match)
    }

    // 테스트용 더미 디렉터리 및 파일 삭제 (실제 사용 시에는 불필요합니다)
    os.RemoveAll("logs") // 디렉터리 전체를 삭제합니다.
}
패턴 'logs/app_????????.log'에 일치하는 파일:
logs/app_20231026.log
logs/app_20231027.log

 

 

이 예시에서는 '?' 와일드카드를 사용하여 'app_' 뒤에 정확히 8개의 문자가 오는 '.log' 파일을 찾고 있습니다.

 

이는 날짜 형식의 파일명을 다룰 때 매우 유용합니다.

 

중요하게 고려해야 할 점

'filepath.Glob'을 사용할 때 몇 가지 중요한 사항을 기억해야 합니다.

  1. '패턴 문법의 정확성': 패턴은 유효한 글로브 패턴이어야 합니다. 예를 들어, 대괄호 '['를 열고 닫지 않으면 'filepath.ErrBadPattern'과 같은 오류가 반환될 수 있습니다. 패턴을 작성할 때는 문법 규칙을 정확히 따르는 것이 중요합니다.
  2. '일치하는 파일이 없는 경우': 만약 주어진 패턴과 일치하는 파일이 전혀 없다면, 'Glob' 함수는 'nil' 에러와 함께 '빈 슬라이스'를 반환합니다. 즉, 에러가 없다고 해서 반드시 매칭되는 파일이 있다는 의미는 아니므로, 반환된 'matches' 슬라이스의 길이를 확인하여 매칭 여부를 판단해야 합니다.
  3. '절대 경로와 상대 경로': 'Glob' 함수는 절대 경로 패턴과 상대 경로 패턴 모두를 처리할 수 있습니다. 패턴이 탐색하고자 하는 경로를 정확하게 반영하는지 확인해야 합니다. 예를 들어, /var/log/*.log는 절대 경로 패턴이고, ../config/*.conf는 상대 경로 패턴입니다.
  4. '플랫폼 독립성': Go의 'path/filepath' 패키지는 운영체제별 경로 구분자(Unix/Linux의 '/' 또는 Windows의 '')를 자동으로 처리합니다. 따라서 대부분의 경우 개발자가 직접 경로 구분자를 신경 쓸 필요는 없습니다. 하지만 'filepath.Join'과 같은 유틸리티 함수를 사용하여 경로를 구성하는 것은 항상 좋은 습관입니다.
  5. '숨김 파일 처리': Unix/Linux 시스템에서는 파일명이나 디렉터리명이 '.'으로 시작하면 숨김 파일로 간주됩니다. 'filepath.Glob'의 '' 와일드카드는 기본적으로 '.'으로 시작하는 숨김 파일을 매칭하지 않습니다. 숨김 파일까지 포함하려면 패턴을 . 또는 .* 등으로 명시적으로 지정해야 합니다. 예를 들어, .* 또는 `..conf`와 같이 사용합니다.

 

예상되는 문제 및 해결책

Go 언어에서 'filepath.Glob'을 사용하다가 마주칠 수 있는 몇 가지 흔한 문제와 그에 대한 해결책을 알아보겠습니다.

  1. '매치 결과가 나오지 않아요!' 또는 '예상했던 파일이 검색되지 않아요!':
    • 원인:
      • 첫째, 패턴 자체의 오타나 문법 오류가 있을 수 있습니다. 예를 들어, '.*.go' 대신 '.*go'와 같이 잘못된 와일드카드 사용이 원인일 수 있습니다.
      • 둘째, 패턴에 지정된 경로에 실제 파일이 존재하지 않거나, 파일명이 대소문자(Linux) 혹은 숨김 파일(Unix/Linux) 때문에 매칭되지 않을 수 있습니다.
    • 해결책:
      • 패턴 문자열을 다시 한번 꼼꼼히 확인하고, 와일드카드의 의미를 정확히 이해하고 있는지 점검합니다.
      • 실제로 해당 경로에 파일이 존재하는지, 그리고 파일명이 패턴과 대소문자까지 정확히 일치하는지 확인합니다. (Windows와 macOS는 기본적으로 파일 시스템에서 대소문자를 구분하지 않지만, Linux는 구분합니다.)
      • 숨김 파일 문제라면, ls -a (Linux/macOS) 또는 dir /a (Windows) 명령어로 숨김 파일을 확인하고, 패턴에 .을 명시적으로 포함하여 시도해 봅니다. 예를 들어, .*.conf와 같이 사용합니다.
  2. '잘못된 패턴 오류 (filepath.ErrBadPattern)가 발생해요!':
    • 원인: 대괄호('[ ]')를 열고 닫지 않았거나, 다른 패턴 문법을 잘못 사용하여 'Glob' 함수가 패턴을 유효한 것으로 인식하지 못할 때 발생합니다.
    • 해결책: 'path/filepath' 패키지의 글로브 패턴 규칙을 다시 확인하고, 패턴 내의 모든 와일드카드 문법이 올바르게 사용되었는지 검토합니다. 특히 [ 문자를 사용했다면 반드시 ] 문자로 닫아주어야 합니다.
  3. '대규모 파일 시스템에서 성능이 저하되거나 메모리 사용량이 많아져요!':
    • 원인: 'filepath.Glob'은 매칭되는 모든 파일의 이름을 메모리에 로드합니다. 만약 수만 개 이상의 파일이 패턴에 매칭되거나, 검색 대상 디렉터리가 매우 깊고 파일이 많을 경우, 성능 병목 현상이 발생하거나 메모리 부족 문제가 생길 수 있습니다.
    • 해결책:
      • 파일 수가 매우 많거나 메모리 제약이 있는 환경에서는 'filepath.Glob' 대신 'filepath.Walk' 또는 'os.ReadDir' 함수를 사용하여 디렉터리를 수동으로 탐색하고, 각 파일에 대해 필요한 패턴 매칭을 직접 수행하는 방식을 고려해야 합니다. 이 방법은 파일을 하나씩 처리하거나 특정 조건에서 탐색을 중단할 수 있어 효율적입니다.
      • 또는 고급 글로빙 패키지인 'gobwas/glob'처럼 패턴을 미리 컴파일하여 재사용하는 방식이 일부 성능 개선에 도움이 될 수 있습니다.

 

심화 학습 및 다음 단계

Go 언어의 'filepath.Glob'은 파일 시스템 작업을 단순화하는 강력한 도구입니다.

 

하지만 모든 시나리오에 적합한 유일한 해결책은 아닙니다.

 

글로빙의 한계를 이해하고 다른 접근법을 아는 것은 개발자로서의 시야를 넓히는 데 중요합니다.

다른 접근법: 언제 'Glob' 대신 다른 것을 고려할까요?

  1. '정규 표현식 (Regular Expressions)':
    • 개념: Go의 'regexp' 패키지를 사용하여 문자열 패턴을 매칭하는 방식입니다. 글로브 패턴보다 훨씬 더 복잡하고 유연한 패턴을 정의할 수 있습니다.
    • 장점: 매우 정교한 문자열 매칭이 가능하여, 파일 이름뿐만 아니라 파일 내용 안의 특정 패턴을 찾거나 데이터 파싱에 적합합니다.
    • 단점: 학습 곡선이 높고, 글로브 패턴보다 일반적으로 성능 오버헤드가 더 큽니다. 파일명 매칭처럼 간단한 용도에는 과도하게 복잡할 수 있습니다.
    • 고려 시점: 파일 이름 패턴이 'filepath.Glob'으로 표현하기에 너무 복잡하거나, 파일 내용까지 분석해야 할 때 고려합니다.
  2. '디렉터리 수동 탐색 (os.ReadDir 또는 filepath.Walk)':
    • 개념: 'os.ReadDir' 함수는 특정 디렉터리 내의 모든 엔트리(파일, 디렉터리)를 읽어오고, 'filepath.Walk' 함수는 지정된 경로 아래의 모든 파일과 디렉터리를 재귀적으로 탐색하면서 콜백 함수를 실행합니다.
    • 장점: 파일 수가 매우 많거나, 각 파일에 대해 복잡한 로직을 적용해야 할 때, 혹은 특정 조건에서 탐색을 중단해야 할 때 유연하게 제어할 수 있습니다. 메모리 사용량을 예측하기 쉽습니다.
    • 단점: 패턴 매칭 로직을 개발자가 직접 구현해야 하므로 'Glob'보다 코드가 길어지고 복잡해질 수 있습니다.
    • 고려 시점: 'filepath.Glob'의 성능이나 메모리 한계에 부딪히거나, 글로빙만으로는 표현할 수 없는 복잡한 파일 처리 로직이 필요할 때 사용합니다.

고급 글로빙: 'gobwas/glob' 패키지

Go 표준 라이브러리의 'filepath.Glob'은 유용하지만, 'a{b,c}d'와 같이 중괄호를 사용하여 여러 대안 패턴을 지정하는 기능은 지원하지 않습니다.

 

이러한 고급 패턴 매칭이 필요할 때는 외부 라이브러리를 활용할 수 있습니다.

 

'github.com/gobwas/glob'은 이러한 요구사항을 충족하는 인기 있는 서드파티 패키지입니다.

 

'gobwas/glob'은 패턴을 미리 '컴파일(Compile)'하여 여러 번 재사용할 수 있게 해주는데, 이는 매칭 성능을 최적화하는 데 도움이 됩니다.

 

패키지를 설치하려면 다음 명령어를 실행합니다.

go get github.com/gobwas/glob
package main

import (
    "fmt"
    "log"

    "github.com/gobwas/glob" // gobwas/glob 패키지를 임포트합니다.
)

func main() {
    // '{*.go,*.md}' 패턴은 '.go' 또는 '.md' 확장자를 가진 파일을 매칭합니다.
    g, err := glob.Compile("{*.go,*.md}")
    if err != nil {
        log.Fatalf("패턴 컴파일 중 오류 발생: '%v'", err)
    }

    files := []string{"main.go", "README.md", "example.txt", "docs/index.md"}

    fmt.Println("gobwas/glob을 사용하여 패턴 '{*.go,*.md}'에 일치하는 파일:")
    for _, file := range files {
        if g.Match(file) { // 컴파일된 패턴 'g'를 사용하여 파일 이름을 매칭합니다.
            fmt.Println(file)
        }
    }
}
gobwas/glob을 사용하여 패턴 '{*.go,*.md}'에 일치하는 파일:
main.go
README.md

 

 

이 예시에서는 'gobwas/glob' 패키지를 사용하여 '.go' 또는 '.md' 확장자를 가진 파일을 매칭하고 있습니다.

 

'Compile' 함수는 패턴을 한 번 파싱하고 준비하여, 여러 파일에 대해 'Match' 함수를 반복적으로 호출할 때 효율성을 높여줍니다.

 

이는 대규모 파일 목록을 처리할 때 특히 유리합니다.

 

결론

파일 글로빙은 파일 시스템 작업을 자동화하고 관리하는 데 있어 매우 유용한 기술입니다.

 

Go 언어의 'path/filepath' 패키지는 기본적인 글로빙 기능을 간결하게 제공하며, 이는 많은 일반적인 시나리오에 충분히 강력합니다.

 

'filepath.Glob'은 그 단순함과 효율성 덕분에 빌드 스크립트, 로그 처리, 설정 파일 관리 등 다양한 Go 애플리케이션에서 활용될 수 있습니다.

 

하지만 더 복잡한 패턴 매칭이나, 극단적으로 많은 파일을 처리해야 하는 경우에는 'regexp' 패키지를 이용한 정규 표현식, 'os.ReadDir'이나 'filepath.Walk'를 이용한 수동 디렉터리 탐색, 또는 'gobwas/glob'과 같은 서드파티 라이브러리를 고려하는 것이 현명합니다.

 

이 글이 Go 언어의 파일 글로빙 개념을 명확히 이해하고, 여러분의 프로젝트에서 파일을 효율적으로 다루는 데 실질적인 도움이 되었기를 바랍니다.

 

다양한 글로빙 패턴을 직접 시도해 보면서 Go의 강력한 파일 처리 능력을 경험해 보시기를 권장합니다.