【Go言語】ファイル/ディレクトリ操作方法 – 基本

目次

はじめに

Goにおける基本のファイル/ディレクトリ操作方法をまとめました。今回の記事では次の要素が登場します。

  • osパッケージ
    • IsNotExist
    • Mkdir
    • MkdirAll
    • Remove
    • RemoveAll
    • Rename
  • os.File
    • Close
    • Create
    • Open
    • OpenFile
    • Read
    • ReadAt
    • Stat
    • Write
    • WriteAt
  • io/ioutilパッケージ
    • ReadFile
    • ReadAll
    • WriteFile

bufioパッケージを用いた操作方法については、それのみで一つの記事になるくらい長くなりそうなので、今後別の記事で書こうと思います。

ファイル/ディレクトリ操作

ファイルの生成

ファイルの生成にはfunc Create(name string) (*File, error)関数を使います。

package main

import (
	"log"
	"os"
)

func main() {
	f, err := os.Create("hello.txt")
	if err != nil {
		log.Fatal(err)
	}
	defer f.Close()
}

Create関数の内部実装を見ると、OpenFile関数が使用されており、第二引数にO_RDWR|O_CREATE|O_TRUNCというフラグが設定されていることがわかります。詳しくは後述の項目で説明しますが、作成したファイルは*File型で返却され、そのまま読み書きできます。また、もしCreate関数で指定したファイルが既に存在している場合、内容が全て切り捨てられ、空のファイルとして開かれます。

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

ディレクトリの作成

ディレクトリの作成にはfunc Mkdir(name string, perm FileMode) error関数、もしくはfunc MkdirAll(path string, perm FileMode) error関数を使用します。ディレクトリ一つを作成する場合は前者、サブディレクトを階層的に合わせて作成する場合は後者の関数を使います。

package main

import (
	"log"
	"os"
)

func main() {
	if err := os.Mkdir("Hoge", 0755); err != nil {
		log.Fatal(err)
	}
}
package main

import (
	"log"
	"os"
)

func main() {
	if err := os.MkdirAll("Hoge/Hello", 0755); err != nil {
		log.Fatal(err)
	}
}

Mkdir関数では、第一引数で指定したフォルダが既に存在すると、*PathErrorエラーが返却されます。MkdirAll関数では、フォルダが存在していてもnilが返却され、エラーは返却されないので注意してください。

2021/08/13 18:06:44 mkdir Hoge: file exists

ファイル/ディレクトリの存在確認

ファイル/ディレクトリの存在確認は、func (f *File) Stat() (FileInfo, error)関数で返却されたエラーをfunc IsNotExist(err error) bool関数を用いてチェックすることで確認します。

package main

import (
	"fmt"
	"os"
)

func main() {
	_, err := os.Stat("aaaaa.txt")
	if os.IsNotExist(err) {
		fmt.Println("file does not exist")
	}
}

ファイルを開く

ファイルを開く際には、func Open(name string) (*File, error)メソッド、もしくはfunc OpenFile(name string, flag int, perm FileMode) (*File, error)メソッドを使います。

package main

import (
	"log"
	"os"
)

func main() {
	f, err := os.Open("hello.txt")
	if err != nil {
		log.Fatal(err)
	}
	defer f.Close()
}

OpenFileメソッドは第二引数に下記フラグ、第三引数にファイルのパーミッションを指定します。フラグはosパッケージのfile.goに定義されています。

const (
	// Exactly one of O_RDONLY, O_WRONLY, or O_RDWR must be specified.
	O_RDONLY int = syscall.O_RDONLY // open the file read-only.
	O_WRONLY int = syscall.O_WRONLY // open the file write-only.
	O_RDWR   int = syscall.O_RDWR   // open the file read-write.
	// The remaining values may be or'ed in to control behavior.
	O_APPEND int = syscall.O_APPEND // append data to the file when writing.
	O_CREATE int = syscall.O_CREAT  // create a new file if none exists.
	O_EXCL   int = syscall.O_EXCL   // used with O_CREATE, file must not exist.
	O_SYNC   int = syscall.O_SYNC   // open for synchronous I/O.
	O_TRUNC  int = syscall.O_TRUNC  // truncate regular writable file when opened.
)
package main

import (
	"log"
	"os"
)

func main() {
	f, err := os.OpenFile("hello.txt", os.O_RDWR|os.O_CREATE, 0755)
	if err != nil {
		log.Fatal(err)
	}
	defer f.Close()
}

ファイルを閉じる

ファイルの閉じる場合には、func (f *File) Close() errorメソッドを使用します。上記の例のように、os.Openメソッドもしくはos.OpenFileメソッドでファイルを開くと、戻り値で*File構造体が返却されますが、その構造体が開いたファイルを閉じるCloseメソッドを持っています。Goではdefer文を用いて関数の終了時にCloseすることが慣習的です。ファイルを閉じ忘れてしまうとメモリリークやエラーにエラーに繋がるため、必ずCloseメソッドで閉じるようにしましょう。

ファイル/ディレクトリの削除

特定のファイル/ディレクトリを削除する

特定のファイルを削除するには、func Remove(name string) errorメソッドを使用します。

package main

import (
	"log"
	"os"
)

func main() {
	err := os.Remove("hello.txt")
	if err != nil {
		log.Fatal(err)
	}
}

ディレクトリごと全て削除する

ディレクトリごと中身のファイルも含めて全て削除する場合には、func RemoveAll(path string) errorメソッドを使用します。次の例では、指定したHogeディレクトリごと配下のファイル/ディレクトリ含めて全て削除されます。

package main

import (
	"log"
	"os"
)

func main() {
	err := os.RemoveAll("Hoge")
	if err != nil {
		log.Fatal(err)
	}
}

ファイルのリネーム/移動

ファイルのリネーム/移動にはfunc Rename(oldpath, newpath string) errorメソッドを使用します。第一引数で指定したファイルを、第二引数で指定したパスにリネーム/移動するメソッドになります。次の例では、このメソッドによりリネームと移動を同時に実施しています。

package main

import (
	"log"
	"os"
)

func main() {
	err := os.Rename("hello.txt", "Hoge/hello2.txt")
	if err != nil {
		log.Fatal(err)
	}
}

ファイルの読み書き

os.Fileによる読み書き

読み込み

os.Fileによる読み込み操作には以下のメソッドを使用します。
func (f *File) Read(b []byte) (n int, err error)
func (f *File) ReadAt(b []byte, off int64) (n int, err error)

これらメソッドを呼び出すと、第一引数に指定した変数に読み込んだ内容が格納されます。後者のReadAtメソッドでは読み込みの開始位置を第二引数に指定することができるため、途中から読み込みを始めたい場合に利用します。

package main

import (
	"fmt"
	"log"
	"os"
)

func main() {
	f, err := os.Open("hello.txt")
	if err != nil {
		log.Fatal(err)
	}
 defer f.Close()

	buf := make([]byte, 64)
	n, err := f.Read(buf)
	if err != nil {
		log.Fatal(err)
	}

	fmt.Println(string(buf[:n]))
}
Hello World!!
こんにちは

ReadAtメソッドを使用すると次の出力結果となります。

package main

import (
	"fmt"
	"io"
	"log"
	"os"
)

func main() {
	f, err := os.Open("hello.txt")
	if err != nil {
		log.Fatal(err)
	}
 defer f.Close()

	buf := make([]byte, 64)
	n, err := f.ReadAt(buf, 6)
	if err != io.EOF && err != nil {
		log.Fatal(err)
	}

	fmt.Println(string(buf[:n]))
}
World!!
こんにちは

書き込み

os.Fileによる書き込み操作には以下のメソッドを使用します。
func (f *File) Write(b []byte) (n int, err error)
func (f *File) WriteAt(b []byte, off int64) (n int, err error)

また、os.OpenFileメソッドでファイルを開く際のフラグ指定の仕方で動作が変わるため注意が必要です。
Writeメソッドでは、os.O_APPENDフラグ設定しないと先頭から上書きされ、設定すると末尾に追記される挙動となります。
WriteAtメソッドは、第二引数で指定した箇所から上書きを行うメソッドであるため、os.O_APPENDフラグを設定するとエラーが返却されます。また、上書きのみを行い、上書き無しの追記指定はできません。

package main

import (
	"io"
	"log"
	"os"
)

func main() {
	f, err := os.OpenFile("hello.txt", os.O_RDWR|os.O_APPEND, 0755)
	if err != nil {
		log.Fatal(err)
	}

	kakikomi := []byte("Write Test\n")
	_, err = f.Write(kakikomi)
	if err != io.EOF && err != nil {
		log.Fatal(err)
	}
}

ioutilによる読み書き

ファイルから読み込む

ファイルからテキストを読み込む場合にはfunc ReadFile(filename string) ([]byte, error)メソッドを使用します。

package main

import (
	"fmt"
	"io/ioutil"
	"log"
)

func main() {
	b, err := ioutil.ReadFile("hello.txt")
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(string(b))
}

io.Readerから読み込む

io.Readerインターフェースを準拠している型から読み込む場合にはfunc ReadAll(r io.Reader) ([]byte, error)メソッドを使用します。
io.Readerインターフェースは次のように定義されています。つまり、Read(p []byte) (n int, err error)メソッドを実装している型はio.Readerインターフェースを準拠していることになります。

type Reader interface {
	Read(p []byte) (n int, err error)
}

次の例ではstrings.NewReader型を使用しました。Readメソッドを実装しているため、io.Readerインターフェースに準拠している型の一つになります。

package main

import (
	"fmt"
	"io/ioutil"
	"log"
	"strings"
)

func main() {
	r := strings.NewReader("Hello World!!\nこんにちは")
	b, err := ioutil.ReadAll(r)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(string(b))
}

ファイルへ書き込む

ファイルへ書き込む場合にはfunc WriteFile(filename string, data []byte, perm fs.FileMode) errorメソッドを使用します。第一引数で指定するファイルに、第二引数で指定するデータを書き込みます。
ファイルが存在しない場合、第三引数で指定するパーミッションでファイルを新規作成します。もしファイルが既に存在する場合は、パーミッションを変更せず、ファイル内テキストを全て切り捨ててからデータの書き込みを行います。

package main

import (
	"io/ioutil"
	"log"
)

func main() {
	data := []byte("Hello World!!\nこんにちは")
	err := ioutil.WriteFile("hello.txt", data, 0755)
	if err != nil {
		log.Fatal(err)
	}
}

まとめ

本記事では主に

・osパッケージによるファイル/ディレクトリ操作
・os.Fileによるファイルの読み書き操作
・ioutilパッケージによるファイルの読み書き操作

についてまとめました。少しでも参考になれば嬉しいです。
閲覧いただきありがとうございました。

他のパッケージについても記事を書いていますので、もし興味がありましたらご参照ください。
【Go言語】database/sqlパッケージによるデータベース操作入門 – sqlite3
【Go言語】encoding/jsonパッケージでJSONをパースする
【Go言語】net/httpパッケージでAPIリクエストを送信する

よかったらシェアしてね!

コメント

コメントする

CAPTCHA


目次
閉じる