Go에는 입출력 추상화 인터페이스인 io.Reader
io.Writer
가 존재합니다.
io/fs
는 파일 시스탬 추상화 인터페이스를 제공합니다.
os
패키지에서 io/fs
구현
// io/fs package
type FS interface {
Open(name string) (File, error)
}
// io/fs package
type File interface {
Stat() (FileInfo, error)
Read([]byte) (int, error)
Close() error
}
// os package
func DirFS(dir string) fs.FS {
return dirFS(dir)
}
func containsAny(s, chars string) bool {
for i := 0; i < len(s); i++ {
for j := 0; j < len(chars); j++ {
if s[i] == chars[j] {
return true
}
}
}
return false
}
type dirFS string
func (dir dirFS) Open(name string) (fs.File, error) {
if !fs.ValidPath(name) || runtime.GOOS == "windows" && containsAny(name, `\:`) {
return nil, &PathError{Op: "open", Path: name, Err: ErrInvalid}
}
f, err := Open(string(dir) + "/" + name)
if err != nil {
return nil, err // nil fs.File
}
return f, nil
}
os
패키지의 DirFS
함수는 디렉토리의 경로를 받아와서 dirFS
로 형변환 합니다. dirFS
는 결국 내부적으로는 string
타입입니다.
fs.FS
인터페이스를 구현하기 위해 os
패키지에서 Open
함수를 정의 했습니다. 이 함수는 FS
의 값을 string
으로 형 변환 해서, 인자로 받은 파일 이름을 붙여 파일을 읽기모드 로 오픈 후 fs.File
인터페이스로 리턴 합니다.
요는 os
패키지에서 io/fs
는 fs.FS
는 디렉토리로, 사용 되고, fs.FS
인터페이스 구현 함수인 Open
은 디렉토리의 하위 파일을 오픈하는 함수 입니다.
예) 파일 읽기
package main
import (
"fmt"
"io/fs"
"io/ioutil"
"os"
)
func main() {
// main파일의 위치를 FS로 얻어온다.
wd := os.DirFS("./")
// #1
// wd 디렉토리에 "test.txt"를 오픈 한다. 이는 fs.File 인터체이스를 리턴 한다.
modFile, err := wd.Open("test.txt")
if err != nil {
panic(err)
}
// fs.File은 io.Reader를 포함 하므로,
// io.Reader를 받는 ioutil.Reader로 파일을 읽는다.
data, err := ioutil.ReadAll(modFile)
if err != nil {
panic(err)
}
fmt.Printf("%s", data)
// #2
// 혹은, io/fs의 제공함수인 ReadFile 함수로 파일을 읽는다.
data, err = fs.ReadFile(wd, "test.txt")
if err != nil {
panic(err)
}
fmt.Printf("%s", data)
}
io/fs
의 함수
func Glob(fsys FS, pattern string) (matches []string, err error)
패턴과 일치하는 모든 파일의 이름을 반환하거나 일치하는 파일이 없으면 nil을 반환합니다.
func main() {
wd := os.DirFS("./")
mats, err := fs.Glob(wd, "go.*")
if err != nil {
panic(err)
}
fmt.Printf("%#v\n", mats)
}
[]string{"go.mod", "go.sum"}
func ReadFile(fsys FS, name string) ([]byte, error)
fsys
하위 파일 name
을 EOF 까지 읽습니다. 내부적으로 Open
Read
Close
함수가 사용 됩니다.
func main() {
wd := os.DirFS("./")
data, err := fs.ReadFile(wd, "main.go")
if err != nil {
panic(err)
}
fmt.Printf("%s\n", data)
}
.
.
.
func main() {
wd := os.DirFS("./")
data, err := fs.ReadFile(wd, "main.go")
if err != nil {
panic(err)
}
fmt.Printf("%s\n", data)
}
func ValidPath (name string ) bool
입력 받은 경로가 Open
가능한, 유효한 경로인지 검증 합니다. .
..
은 사용 불가 합니다. 루트가 지정되지 않은 슬래시로 구분 된 경로로 입력해야 합니다.
func main() {
p1 := fs.ValidPath("boo/main.go")
p2 := fs.ValidPath("foo/main.go")
fmt.Printf("%t\n%t\n", p1, p2)
}
true
true
func WalkDir(fsys FS, root string, fn WalkDirFunc) error
fsys
에서 root
디렉토리 부터 파일 트리를 탐색 합니다. 발견된 엔트리는 fn
함수로 전달 됩니다.
func main() {
wd := os.DirFS("./")
err := fs.WalkDir(wd, ".", func(path string, d fs.DirEntry, err error) error {
// 탐색한 엔트리 오픈 에러 처리
if err != nil {
println(err)
return nil
}
// 엔트리 경로, 이름 출력
fmt.Printf("%s - %s\n", path, d.Name())
// 만약 파일 이름이 ee 일 경우
if d.Name() == "ee" {
// 에러 리턴, WalkDir 함수 즉시 종료 후 에러 리턴
return errors.New("found ee")
}
return nil
})
if err != nil {
panic(err)
}
}
. - .
dd - dd
dd/ee - ee
panic: found ee
goroutine 1 [running]:
main.main()
/home/dev/project/boo/main.go:28 +0xa8
exit status 2
DirEntry
인터페이스
type DirEntry interface {
Name() string
IsDir() bool
Type() FileMode
Info() (FileInfo, error)
}
func ReadDir(fsys FS, name string) ([]DirEntry, error)
fsys
의 하위 디렉토리 name
디렉토리를 읽습니다.
func main() {
wd := os.DirFS("./")
dent, err := fs.ReadDir(wd, "dd")
if err != nil {
panic(err)
}
for _, n := range dent {
println(n.Name())
}
}
func Sub(fsys FS, dir string) (FS, error)
fsys
의 하위 디렉토리 name
을 받아 io/fs
로 오픈 하여 리턴 합니다.