はじめに
Golangでファイルに関するインターフェースの一つ、fsパッケージについて調べたのでメモ
※2021/2/26 Go1.16より、抽象化されたfsパッケージが実装されたという情報をコメントで頂いたので、記事内容を修正しました。
io/fsパッケージとは
ファイルシステムの基本的なインターフェースです。fsパッケージが実装される以前は各ファイルシステムごとにメソッド名や使い方が異なっていました。
参考程度にfsパッケージの導入によるメリットとして、テストのパフォーマンスや欠陥、テストコードの書きやすさを改善できる点を挙げる記事がありました。
fs.FS
FS(ファイルシステム)の定義は、Openというメソッドが実装されているものとなっています。
type FS interface {
Open(name string) (File, error)
}
FS自体は最小限の構成になっていて、StatFSのような追加のインターフェースを実装することもできます。
type StatFS interface {
FS
Stat(name string) (FileInfo, error)
}
StatFSはFSに加えてStatのメソッドが実装されているインターフェースです。
他にもいくつか標準で用意されているので公式ドキュメントを参照するとよいです。
fs.FileInfo
type FileInfo interface {
Name() string // base name of the file
Size() int64 // length in bytes for regular files; system-dependent for others
Mode() FileMode // file mode bits
ModTime() time.Time // modification time
IsDir() bool // abbreviation for Mode().IsDir()
Sys() interface{} // underlying data source (can return nil)
}
ファイルの情報に関するインターフェース
FileInfo自体はfs.Stat()で得ることができます。
fs.Stat()
func Stat(fsys FS, name string) (FileInfo, error)
ルートディレクトリのファイルシステム型とファイル名を指定すれば、そのファイルに関する属性FileInfoを返します。
FSにはOpenが実装されているデータ型やFS型を直接指定するとよいです。
os.DirFS()
FSを直接返してくれる関数
指定したディレクトリをルートとするファイルツリーのファイルシステム(fs.FS)を返します。
func DirFS(dir string) fs.FS
FileInfoを取得するサンプル
準備として、任意のディレクトリにtest.txtという空のテキストファイルを作成しました。
package main
import (
"io/fs"
"log"
"os"
)
func main() {
root := "C:/Users/xxxx/Documents"
filename := "test.txt"
fileSystem := os.DirFS(root)
fileinfo, err := fs.Stat(fileSystem,filename)
if err != nil {
log.Fatal(err)
}
log.Println(fileSystem) // C:\Users\xxxx\Documents
log.Println(fileinfo.Name()) // test.txt
log.Println(fileinfo.Size()) // 0
log.Println(fileinfo.Mode()) // -rw-rw-rw-
log.Println(fileinfo.ModTime()) // 2021-11-28 11:47:28.5960394 +0900 JST
log.Println(fileinfo.IsDir()) // false
log.Println(fileinfo.Sys()) // &{32 {1214173898 30925826} {1214173898 30925826} {1214173898 30925826} 0 0}
log.Println(reflect.TypeOf(fileinfo.Sys())) // *syscall.Win32FileAttributeData
}
archive/zipでFileInfoを取得するサンプル
zip.ReaderはOpen()が実装されているため、FS型です。
zip.Readerはzip.OpenReaderで取得できます
準備として、test1.csv, test2.csv, test3.csvを圧縮したtest.zipを用意しました。
import (
"archive/zip"
"io/fs"
"log"
"reflect"
)
func main() {
root := "C:/Users/xxxx/Documents/test.zip"
// get Reader r struct
r, err := zip.OpenReader(root)
if err != nil {
log.Fatal(err)
}
defer r.Close()
filename := "test1.csv"
fileinfo, err := fs.Stat(r,filename)
if err != nil {
log.Fatal(err)
}
log.Println(fileinfo.Name()) // test1.csv
log.Println(fileinfo.Size()) // 432
log.Println(fileinfo.Mode()) // -rw-rw-rw-
log.Println(fileinfo.ModTime()) // 2021-11-30 18:25:56 +0000 UTC
log.Println(fileinfo.IsDir()) // false
log.Println(fileinfo.Sys()) // &{test1.csv false 20 20 0 8 2021-11-30 18:25:56 +0000 UTC 37692 21374 3656633017 299 432 299 432 [] 32}
log.Println(reflect.TypeOf(fileinfo.Sys())) // *zip.FileHeader
}
FileInfo.Sys()はドキュメントには各ファイルシステムのデータソースを返すと書いてあります。
zipではzip.FileHeaderが返されました。
ついでにファイルヘッダーとは、zipファイルの中にあるファイルなどの情報のことです
ファイル自体の中身はファイルエントリと呼ばれます。
最後に
コメントで情報共有いただいた@kechakoさん、ありがとうございました。