0
0

【Go】ファイル操作

Last updated at Posted at 2024-07-10

はじめに

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

ファイルをオープンして書き込む

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

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

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"という指定されたファイルにデータを書き込み、適切な権限を設定しています。

err := os.WriteFile("data1", data, 0644)

WriteFileの定義は次の通りです。エラーがある場合のみ返り値があります。

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と権限を与えています。つまり、所有者は読み書きができ、グループと他のユーザーは読み取り専用であることを意味します。

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

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

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

os.ReadFile

続いて、WriteFileで作成したdata1というファイルを以下の箇所で読み込みます。

read1, _ := os.ReadFile("data1")

メソッドの定義は長いので抜粋しますが、簡潔に説明すると、

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
		}
...

ファイルを開いた後に、開いたファイルからデータを読み込み、そのデータをメモリ上のバッファに格納します。そして、読み込んだデータをバイトスライスで返却します。ファイルの読み込み中にエラーが発生した場合はそのエラーも返します。

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

os.Create

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

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

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

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

定義は以下の通りです。
OpenFileでは権限が0666で全ユーザーに読み書き権限が与えられています(さすがに設定できると思うのですが、探しきれませんでした。)

func Create(name string) (*File, error) {
	return OpenFile(name, O_RDWR|O_CREATE|O_TRUNC, 0666)
}

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

*File.Write

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

bytes, _ := file1.Write(data)

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

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との違いは、フラグとパーミッションです。

定義は以下の通りです。

func Open(name string) (*File, error) {
	return OpenFile(name, O_RDONLY, 0)
}

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

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

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

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

*File.Read

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

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

bytes, _ = file2.Read(read2)

こちらも*Fileのメソッドで、以下の通り定義されています。

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の以下の箇所でも使用されています。

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と何度か説明していましたが、それがどのようなものなのか記述していなかったので追記です。

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
}
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0