【Go】ファイル操作

# はじめに
今回はGo言語でのファイルの読み書きの方法を今回はまとめてみました。

# ファイルをオープンして書き込む
まず、一般的なファイルを開いて書き込む方法についてです。
以下は、読み書きを2種類の方法で行っています。data1とdata2というファイル名で中にHello World!と書き込むコードです。

:::note info
蛇足ですが、Go1.16以降ではioutilパッケージのいくつかの関数が推奨されなくなり、osパッケージに移動されています。今回のWriteFileとReadFileも元々はioutilの関数でしたが使おうとすると警告が出ます。
:::

```go
package main

import (
	"fmt"
	"os"
)

func main() {
	data := []byte("Hello World!\n")
	err := os.WriteFile("data1", data, 0644)
	if err != nil {
		panic(err)
	}
	read1, _ := os.ReadFile("data1")
	fmt.Print(string(read1))

	// os.WriteFile()もあるけど何だこれ
	file1, _ := os.Create("data2")
	defer file1.Close()

	bytes, _ := file1.Write(data)
	fmt.Printf("Wrote %d bytes to file\n", bytes)

	file2, _ := os.Open("data2")
	defer file2.Close()

	read2 := make([]byte, len(data))
	bytes, _ = file2.Read(read2)
	fmt.Printf("Read %d bytes from file\n", bytes)
	fmt.Println(string(read2))
}
```

コードが長いため重要な部分を抜粋して確認します。
## os.WriteFile
この関数は、指定したファイルにデータの内容を書き込みます。ファイルが存在しない場合は新しいファイルが作成され、既存のファイルがある場合はその内容が上書きされます。

簡潔に説明すると、ファイルが存在しない場合、"data1"という指定されたファイルにデータを書き込み、適切な権限を設定しています。

```go
err := os.WriteFile("data1", data, 0644)
```
`WriteFile`の定義は次の通りです。エラーがある場合のみ返り値があります。
```go
func WriteFile(name string, data []byte, perm FileMode) error {
	f, err := OpenFile(name, O_WRONLY|O_CREATE|O_TRUNC, perm)
	if err != nil {
		return err
	}
	_, err = f.Write(data)
	if err1 := f.Close(); err1 != nil && err == nil {
		err = err1
	}
	return err
}
```
そして、引数で指定する値は以下の通りです。
- 第一引数：ファイル名や相対パス等で指定したファイル
- 第二引数：書き込むデータ（バイトスライス）
- 第三引数：ファイルのパーミッション

今回は`0644`と権限を与えています。つまり、所有者は読み書きができ、グループと他のユーザーは読み取り専用であることを意味します。

:::note info
パーミッションの補足
パーミッションの形式3桁または4桁で表されます。
__3桁形式__
最初の桁（ユーザー）: 所有者の権限
2番目の桁（グループ）: グループの権限
3番目の桁（他のユーザー）: その他のユーザーの権限

__4桁形式__
最初の桁（特別なビット）: setuid, setgid, stickyビット
2番目の桁（ユーザー）: 所有者の権限
3番目の桁（グループ）: グループの権限
4番目の桁（他のユーザー）: その他のユーザーの権限

__パーミッションの値__
読み取り（r）: 4
書き込み（w）: 2
実行（x）: 1
:::

## os.ReadFile
続いて、`WriteFile`で作成した`data1`というファイルを以下の箇所で読み込みます。
```go
read1, _ := os.ReadFile("data1")
```

メソッドの定義は長いので抜粋しますが、簡潔に説明すると、
```go
func ReadFile(name string) ([]byte, error){
...
, err := Open(name)
...
　　 data := make([]byte, 0, size)
	for {
		n, err := f.Read(data[len(data):cap(data)])
		data = data[:len(data)+n]
		if err != nil {
			if err == io.EOF {
				err = nil
			}
			return data, err
		}
...
```
ファイルを開いた後に、開いたファイルからデータを読み込み、そのデータをメモリ上のバッファに格納します。そして、読み込んだデータをバイトスライスで返却します。ファイルの読み込み中にエラーが発生した場合はそのエラーも返します。

:::note info
ここまでのまとめ
`os.WriteFile(filename, data, permission)`でファイルにデータを書き込み、`os.ReadFile(filename)`でファイルのバイトスライスを取得できる
:::

## os.Create
続いて、`os.Create`です。`os.Create`は`os.File構造体のポインタ`を返します。

この関数は、指定された名前の新しいファイルを作成します。もし同じ名前のファイルが既に存在する場合、そのファイルは上書きされます。

`os.WriteFile`と似ていますが、`os.WriteFile`は書き込みまで行ってくれる一方で、`os.Create`はファイルの作成をするだけで。別途、書き込み、クローズを分けて行う必要があるため、より細かい制御が可能です。


```go
file1, _ := os.Create("data2")
```

定義は以下の通りです。
`OpenFile`では権限が`0666`で全ユーザーに読み書き権限が与えられています（さすがに設定できると思うのですが、探しきれませんでした。）
```go
func Create(name string) (*File, error) {
	return OpenFile(name, O_RDWR|O_CREATE|O_TRUNC, 0666)
}
```

興味深いのが`os.ReadFile`と`os.Create`ともに`Open関数`を使用していますが、`os.ReadFile`は開いた`os.File構造体のポインタ`を処理してバイトスライスを返却する一方で、`os.Create`は処理する前の`os.File構造体のポインタ`を返すという点です。

## *File.Write
そして、上述の`os.Create`で返却されたFile構造体に紐づいた`Write`メソッドを使用してデータを書き込みます。

```go
bytes, _ := file1.Write(data)
```
`Writeメソッド`の定義は以下の通りです。
この関数ではデータをバイトスライスで受け取り、`f.write`で書き込み、書き込んだバイト数と発生したエラーを返却します。

```go
func (f *File) Write(b []byte) (n int, err error) {
	if err := f.checkValid("write"); err != nil {
		return 0, err
	}
	n, e := f.write(b)
	if n < 0 {
		n = 0
	}
	if n != len(b) {
		err = io.ErrShortWrite
	}

	epipecheck(f, e)

	if e != nil {
		err = f.wrapErr("write", e)
	}

	return n, err
}
```


## os.Open
次に`os.Open`ですが、これは上述の`os.Create`に非常によく似ていますが、名前の通り、ファイルを作成することはなく、既存のファイルを読み取り専用で開きます。

ただし、__返り値はos.File構造体のポインタとエラーと同じです。__ `os.Create`との違いは、フラグとパーミッションです。

定義は以下の通りです。
```go
func Open(name string) (*File, error) {
	return OpenFile(name, O_RDONLY, 0)
}
```

このフラグは`OpenFile`関数の以下の部分で使用されており、フラグによって`appendMode`がtrue/falseに切り替わるようになっています。


- trueの場合はファイルにデータを書き込むとき、常にファイルの末尾に追加されます。
- falseの場合はファイルにデータを書き込むとき、現在のファイルポインタの位置に書き込みが行われます。ファイルポインタがファイルの先頭にある場合、既存のデータが上書きされます。

```go
f.appendMode = flag&O_APPEND != 0
```
`os.Open`ではフラグが`O_RDONLY`で`0x0`、O_APPENDは`0x8`と定義されているので、`f.appendMode = flag&O_APPEND != 0`は`0000`と`1000`が`0`となるため、falseとなります。

:::note info
ビットごとのAND演算は、両方のビットが1である場合にのみ1を返し、それ以外の場合、そのビットを0に設定します。
:::

## *File.Read
最後に`os.Open`で返却されたファイルを以下の箇所で読み込みます。`f.Read`は指定されたバイストスライスに読み取ったデータを格納します。

つまり、以下は、ファイルfile2からデータを読み取り、バッファread2に格納するためのコードです。

```go
bytes, _ = file2.Read(read2)
```

こちらも*Fileのメソッドで、以下の通り定義されています。
```go
func (f *File) Read(b []byte) (n int, err error) {
	if err := f.checkValid("read"); err != nil {
		return 0, err
	}
	n, e := f.read(b)
	return n, f.wrapErr("read", e)
}
```

実はこのメソッドは`os.ReadFile`の以下の箇所でも使用されています。
```go
n, err := f.Read(data[len(data):cap(data)])
```
`f.Read`は指定されたバイスとスライスに読み取ったデータを格納するので、`ReadFile`の中では`f.Read`を使用し、開いたファイルをバイト数を確認しながらデータをバイトスライスに格納し、最終的にそのバイトスライスを返却しています。

一方で、`f.Read`だけでは、ただ指定されたバイトスライスにデータを格納するだけです。

つまり、厳密には違いますが、めちゃくちゃざっくり言うと、__os.Openして、os.Readする作業はos.ReadFileで完結する。__ ってことですかね。

# まとめ
細かく説明してしまったので長くなりましたが、最後に表でまとめて一目で分かるようにしておきました。以下に、各関数とメソッドの特徴をまとめた表を作成しました。

| 機能 | 説明 | 使用例 | 返り値 |
|----------------|----------------|-------------------|-------------------|
| `os.WriteFile` | 指定したファイルにデータを書き込む。ファイルが存在しない場合は新規作成、存在する場合は上書き | `os.WriteFile("file_name", []byte, 0644)` | `error` |
| `os.ReadFile`  | 指定したファイルからデータを読み取る。ファイルの内容をバイトスライスとして返す | `read, _ := os.ReadFile("file_name")` | `[]byte, error` |
| `os.Create`    | 指定した名前の新しいファイルを作成する。既存のファイルがある場合は上書き。ファイルハンドルを返す | `file, _ := os.Create("file_name")` | `*os.File, error` |
| `os.Open`      | 指定した名前の既存のファイルを読み取り専用で開く。ファイルハンドルを返す | `file, _ := os.Open("file_name")` | `*os.File, error` |
| `*File.Write`  | 開かれたファイルにデータを書き込む。書き込んだバイト数とエラーを返す | `bytes, _ := file.Write([]byte)` | `int, error` |
| `*File.Read`   | 開かれたファイルからデータを読み取る。読み取ったデータを指定されたバイトスライスに格納 | `bytes, _ = file.Read([]byte)` | `int, error` |

説明の中で`*os.File`と何度か説明していましたが、それがどのようなものなのか記述していなかったので追記です。
```go
type File struct {
	*file // os specific
}

...

type file struct {
	pfd         poll.FD
	name        string
	dirinfo     *dirInfo // nil unless directory being read
	nonblock    bool     // whether we set nonblocking mode
	stdoutOrErr bool     // whether this is stdout or stderr
	appendMode  bool     // whether file is opened for appending
}
```