先日Goでテキストファイルを読み書きする機会があり、その時に調べた自分用メモです。インターネット上には同様の内容の記事が数多く存在しておりますので、そちらも検索&参照してみてください。
私なりのざっくりイメージ
共通の注意点
- main()関数ではなく別関数にしているコードは、ファイルを閉じる処理のdefer呼び出しを有効にするためです。
- 一括の場合、ファイルの内容を全てメモリに保持するのでメモリ不足などの注意が必要です。
- 各関数やメソッドによって改行コードの出力有無が異なるので、その辺りも楽しんでみてください。
読み込み
- 読み込むテキストファイル「read.txt」の内容は次の通りです。
- 各Goコードを実行すると同じ内容が標準出力されます。
$ cat read.txt
12345
あいうえお
1234567890
バイト配列単位
- osパッケージ
func (f *File) Read(b []byte) (n int, err error)
package main
import (
"fmt"
"io"
"os"
)
func main() {
if err := readBytes("read.txt"); err != nil {
fmt.Println(os.Stderr, err)
os.Exit(1)
}
}
func readBytes(filename string) error {
file, err := os.Open(filename)
if err != nil {
return err
}
defer file.Close()
b := make([]byte, 10)
for {
c, err := file.Read(b)
if c == 0 {
break
}
if err == io.EOF {
break
}
if err != nil {
return err
}
line := string(b[:c])
fmt.Print(line)
}
return nil
}
※ 変数 line には改行コードが含まれます。
行(改行コード)単位
- bufioパッケージ
func (s *Scanner) Text() string
package main
import (
"bufio"
"fmt"
"os"
)
func main() {
if err := readLine("read.txt"); err != nil {
fmt.Println(os.Stderr, err)
os.Exit(1)
}
}
func readLine(filename string) error {
file, err := os.Open(filename)
if err != nil {
return err
}
defer file.Close()
scanner := bufio.NewScanner(file)
for scanner.Scan() {
line := scanner.Text()
fmt.Println(line)
}
if err := scanner.Err(); err != nil {
return err
}
return nil
}
※ 変数 line には改行コードは含まれません。
行(区切り文字指定)単位
- bufioパッケージ
func (b *Reader) ReadString(delim byte) (string, error)
package main
import (
"bufio"
"fmt"
"io"
"os"
)
func main() {
if err := readLineDelim("read.txt", '\n'); err != nil {
fmt.Println(os.Stderr, err)
os.Exit(1)
}
}
func readLineDelim(filename string, delim byte) error {
file, err := os.Open(filename)
if err != nil {
return err
}
defer file.Close()
reader := bufio.NewReader(file)
for {
line, err := reader.ReadString(delim)
if err == io.EOF {
break
}
if err != nil {
return err
}
fmt.Print(line)
}
return nil
}
※ ファイルにキャリッジリターンが含まれる場合は変数 line にもキャリッジリターンが含まれます。
行単位
- bufioパッケージ
func (b *Reader) ReadLine() (line []byte, isPrefix bool, err error)
package main
import (
"bufio"
"fmt"
"io"
"os"
)
func main() {
if err := readLine("read.txt"); err != nil {
fmt.Println(os.Stderr, err)
os.Exit(1)
}
}
func readLine(filename string) error {
file, err := os.Open(filename)
if err != nil {
return err
}
defer file.Close()
reader := bufio.NewReader(file)
for {
line, isPrefix, err := reader.ReadLine()
if err == io.EOF {
break
}
if err != nil {
return err
}
fmt.Print(string(line))
if !isPrefix {
fmt.Println()
}
}
return nil
}
※ 変数 line に改行コードは含まれません。ただし isPrefix が true の場合は長い行の途中を読み込んでいる事を示します。
一括
- ioutilパッケージ
func ReadFile(filename string) ([]byte, error)
package main
import (
"fmt"
"os"
"io/ioutil"
)
func main() {
b, err := ioutil.ReadFile("read.txt")
if err != nil {
fmt.Println(os.Stderr, err)
os.Exit(1)
}
lines := string(b)
fmt.Print(lines)
}
書き込み
- 書き出すテキストファイル「write.txt」の内容は次の通りです。
- 各Goコードを実行すると同階層にファイル出力されます。
$ cat write.txt
12345
あいうえお
1234567890
バイト配列単位
- osパッケージ
func (f *File) Write(b []byte) (n int, err error)
package main
import (
"fmt"
"os"
)
var (
lines = []string{"12345\n", "あいうえお\n", "1234567890\n"}
)
func main() {
if err := writeByres("write.txt"); err != nil {
fmt.Println(os.Stderr, err)
os.Exit(1)
}
}
func writeByres(filename string) error {
file, err := os.Create(filename)
if err != nil {
return err
}
defer file.Close()
for _, line := range lines {
b := []byte(line)
_, err := file.Write(b)
if err != nil {
return err
}
}
return nil
}
行(文字列)単位
- osパッケージ
func (f *File) WriteString(s string) (n int, err error)
- fmtパッケージ
func Fprint(w io.Writer, a ...interface{}) (n int, err error)
package main
import (
"fmt"
"os"
)
var (
lines = []string{"12345\n", "あいうえお\n", "1234567890\n"}
)
func main() {
if err := writeLine("write.txt"); err != nil {
fmt.Println(os.Stderr, err)
os.Exit(1)
}
}
func writeLine(filename string) error {
file, err := os.Create(filename)
if err != nil {
return err
}
defer file.Close()
for _, line := range lines {
_, err := file.WriteString(line)
// fmt.Fprint()の場合
// _, err := fmt.Fprint(file, line)
if err != nil {
return err
}
}
return nil
}
バッファリング
- bufioパッケージ
func (b *Writer) Write(p []byte) (nn int, err error)
func (b *Writer) WriteString(s string) (int, error)
- fmtパッケージ(書き出しのみ)
func Fprint(w io.Writer, a ...interface{}) (n int, err error)
package main
import (
"bufio"
"fmt"
"os"
)
var (
lines = []string{"12345\n", "あいうえお\n", "1234567890\n"}
)
func main() {
if err := writeBuffering("write.txt"); err != nil {
fmt.Println(os.Stderr, err)
os.Exit(1)
}
}
func writeBuffering(filename string) error {
file, err := os.Create(filename)
if err != nil {
return err
}
defer file.Close()
writer := bufio.NewWriter(file)
if _, err := writer.Write([]byte(lines[0])); err != nil {
return err
}
if _, err := writer.WriteString(lines[1]); err != nil {
return err
}
if _, err := fmt.Fprint(writer, lines[2]); err != nil {
return err
}
writer.Flush()
return nil
}
一括
- ioutilパッケージ
func WriteFile(filename string, data []byte, perm os.FileMode) error
package main
import (
"fmt"
"io/ioutil"
"os"
)
var (
lines = []string{"12345\n", "あいうえお\n", "1234567890\n"}
)
func main() {
b := []byte{}
for _, line := range lines {
ll := []byte(line)
for _, l := range ll {
b = append(b, l)
}
}
err := ioutil.WriteFile("write.txt", b, 0666)
if err != nil {
fmt.Println(os.Stderr, err)
os.Exit(1)
}
}
成果物
普段は開発モードで管理しているソースコードに対してテスト環境や本番環境にデプロイするために差分となる設定値を書き換えるための「ファイル内文字列置換ツール」を作成しました。対象ファイルのサイズが小さいため今回は一括(ioutilパッケージ)を選択しました。