【fs.FileInfo】ファイルの属性情報【Golang】

# はじめに
Golangでファイルに関するインターフェースの一つ、fsパッケージについて調べたのでメモ

※2021/2/26 Go1.16より、抽象化されたfsパッケージが実装されたという情報をコメントで頂いたので、記事内容を修正しました。

# io/fsパッケージとは
ファイルシステムの基本的なインターフェースです。fsパッケージが実装される以前は各ファイルシステムごとにメソッド名や使い方が異なっていました。

https://text.baldanders.info/golang/file-system-interface-in-go-2/

参考程度にfsパッケージの導入によるメリットとして、テストのパフォーマンスや欠陥、テストコードの書きやすさを改善できる点を挙げる記事がありました。

https://www.gopherguides.com/articles/golang-1.16-io-fs-improve-test-performance

## fs.FS
FS(ファイルシステム)の定義は、Openというメソッドが実装されているものとなっています。

```go:io/fs
type FS interface {
	Open(name string) (File, error)
}
```

FS自体は最小限の構成になっていて、StatFSのような追加のインターフェースを実装することもできます。

```go:io/fs
type StatFS interface {
	FS
	Stat(name string) (FileInfo, error)
}
```
StatFSはFSに加えてStatのメソッドが実装されているインターフェースです。
他にもいくつか標準で用意されているので[公式ドキュメント](https://pkg.go.dev/io/fs)を参照するとよいです。

## fs.FileInfo

```go:io/fs
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()

```go:io/fs
func Stat(fsys FS, name string) (FileInfo, error)
```
ルートディレクトリのファイルシステム型とファイル名を指定すれば、そのファイルに関する属性FileInfoを返します。

FSにはOpenが実装されているデータ型やFS型を直接指定するとよいです。

## os.DirFS()
FSを直接返してくれる関数
指定したディレクトリをルートとするファイルツリーのファイルシステム(fs.FS)を返します。

```go:os
func DirFS(dir string) fs.FS
```


## FileInfoを取得するサンプル

準備として、任意のディレクトリにtest.txtという空のテキストファイルを作成しました。

```go
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を用意しました。

```go
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さん、ありがとうございました。