はじめに
Goにおける基本のファイル/ディレクトリ操作方法をまとめました。今回の記事では次の要素が登場します。
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リクエストを送信する
コメント