Go のディレクトリ操作まわりの関数

今回のトピックも自分のメモの色合いが強い。今回これを書いている理由は、「記憶するため」の一つの手段だ。

今回のお題はとても簡単で

ディレクトリを捜査して、Dockerfile にマッチするものを見つけてそのディレクトリにあるメタデータやディレクトリの名前を取得したいというとっても簡単なもの。問題は私が Go でどう書くかを記憶していないことにある。そこでブログを書いてみて整理してみよう。

# Current Directry

```go
currentDir := os.Getwd()
```

# ファイルパスの結合

ファイルパスの結合はプラットフォームによって異なるので、自分で足すと暗黒。
Join のパラメータは、`...string` であるので可変長である。リストにも簡単に変換できる。 

```go
path := filepath.Join(currentDir, "tests", "images)
```

ちなみに、`...` は Ellipsis という。`[]string` から Ellipsis に変換したいケースは

```go
param := []string{ CurrentDir, "tests", "images" }
path := filepath.Join(param...)
```
としてやると良い。 ちなみに、`filepath.Join(CurrentDir, param...) は動作しない。Join のパラメータはあくまで Ellipsis だけだからだ。

# ディレクトリの検索を行う。

ディレクトリのサーチを行う場合、主に３つの方法がある。

# ディレクトリのリストを取得する

特定のディレクトリのリストの一覧を取得する。

```go
dirs, err := ioutil.ReadDir(path)
```

だ。ちなみに戻り値は、`FileInfo` 構造体でこんな値がある。

```go
// A FileInfo describes a file and is returned by Stat.
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)
}
```

# Glob 
パターンマッチで検索をしたい場合は、Glob を使うことが出来る。

```go
target := filepath.Join(path, "*/Dockerfile"
dirs := filepath.Glob(target)
```
最初は、複数のディレクトリを挟んでもマッチしてくれるのかと期待したが、単なる１階層のマッチの様子だ。

# WalkDir 再帰的に検索する

最後のパターンが、`WalkDir` だ。これは再帰的に検索をしてくれる。Dirといいつつ、ファイルも検索してくれる。ちなみに、`Walk`という関数もあるが、これは、使わなくてよい。go 1.16 から、WalkDir が導入されて、こちらの方が効率が良いらしい。
見ての通り、関数を渡すと、ディレクトリもしくはファイルを見つけるとコールしてくれる。

ちなみに、`filepath.Base()` は、ファイル名のフルパスで、最後の要素（ここではファイル名のみ）を返してくれる。

```go
err = filepath.WalkDir(currentDir, func(path string, d fs.DirEntry, err error) error {
   if !d.Dir() {
      fmt.Printf("Base: %ss\n", filepath.Base(path))
      if d.Name() == "Dockerfile" {
          dockerfile, err := ioutil.ReadFile(path)
          if err != nil {
            panic(err)
          }
       }
   }
   return err
}
```

DirEntry は、ファイル/ディレクトリ名のほかに、ディレクトリかどうかとか、FileModeとか、FileInfoを返してくれる。のでとても便利っぽい。


```go 
// A DirEntry is an entry read from a directory
// (using the ReadDir function or a ReadDirFile's ReadDir method).
type DirEntry interface {
	// Name returns the name of the file (or subdirectory) described by the entry.
	// This name is only the final element of the path (the base name), not the entire path.
	// For example, Name would return "hello.go" not "/home/gopher/hello.go".
	Name() string

	// IsDir reports whether the entry describes a directory.
	IsDir() bool

	// Type returns the type bits for the entry.
	// The type bits are a subset of the usual FileMode bits, those returned by the FileMode.Type method.
	Type() FileMode

	// Info returns the FileInfo for the file or subdirectory described by the entry.
	// The returned FileInfo may be from the time of the original directory read
	// or from the time of the call to Info. If the file has been removed or renamed
	// since the directory read, Info may return an error satisfying errors.Is(err, ErrNotExist).
	// If the entry denotes a symbolic link, Info reports the information about the link itself,
	// not the link's target.
	Info() (FileInfo, error)
}
```

## 実行結果

```bash
Base: go.mod
Base: main.go
Base: activator_test.go
Base: httpoptions_test.go
Base: test_images.go
Base: apps.go
Base: command.go
Base: helm.go
Base: kubectl.go
Base: shared.go
Base: system.go
Base: kafka_test.go
Base: .dockerignore
Base: .gitignore
Base: Dockerfile
```

おなじみの、`ioutil.ReadFile()`　と、 `json.Unmarshall` があれば後は簡単にファイル操作出来そうやな。