SE_BOKUのまとめノート的ブログ

SE_BOKUが知ってること・勉強したこと・考えたことetc

GO言語(golang)1.20:フォルダの再帰処理(WalkDir)とファイル読み書きサンプル

f:id:arakan_no_boku:20210412005751p:plain

目次

GO言語(golang)1.20:フォルダの再帰的処理とファイル読み書きサンプル

GO言語でWindowsのフォルダを再帰的に処理してファイルを読み書きする方法を整理して、簡単なサンプルを作ってみます。。

フォルダを再帰的に処理

まずは、指定したフォルダ以下にあるファイルを再帰的に処理する方法です。

パッケージ「filepath」の「.WalkDir」を使ってます。

pkg.go.dev

サンプルはこちらです。

import (
	"fmt"
	"os"
	"path/filepath"
)

func WalkFolder(searchPath string) {
	filepath.WalkDir(searchPath, func(path string, info os.DirEntry, err error) error {
		if info.IsDir() {
			fmt.Println("フォルダーです")
		} else {
			// pathは相対パス絶対パスにするにはAbsを使う
			p, _ := filepath.Abs(path)
			fmt.Println(p)
			// 拡張子のみ取すにはExtを使う
			if filepath.Ext(path) == ".go" {
				// ファイル名だけを取り出すのはBaseを使う
				fmt.Println("*" + filepath.Base(path))
			}
		}
		return err
	})
}

WalkDirの第一引数は探索をするトップフォルダです。 

第二引数に3つの引数を持つ無名関数(funcのみで名前がない)を渡しています。

トップフォルダ以下にあるファイル・フォルダ1つごとに、この無名関数が実行されてそれぞれの引数にファイル・フォルダに関する情報が格納されます。

  • path string : 対象となっているファイル・フォルダの相対パス
  • info os.DirEntry : 対象となっているファイル・フォルダに関する情報
  • err error : エラー情報(エラーなしなら「nil」)

無名関数の中でそれらの情報を使って色々処理をする感じです。

サンプルは「WalkFolder(".")」ように使うイメージで関数にしています。

ファイルの読みとりサンプル

次は、ファイルの読み取りです。

わかりやすいように、テキストファイルを読んで標準出力に書き出すだけにします。

import (
	"bufio"
	"fmt"
	"os"
)

func cat(srcpath string) bool {
	var err error

	fileIn, err := os.Open(srcpath)
	if err != nil {
		return false
	}
	defer fileIn.Close()

	scanner := bufio.NewScanner(fileIn)
	for scanner.Scan() {
		fmt.Println(scanner.Text())
	}
	if err := scanner.Err(); err != nil {
		panic(err)
	}
	return true
}

srcpathに表示対象ファイルのパスを指定すると、行単位で読んで書き出すだけです。

改行で区切られたテキスト行のファイルなどのデータを読み取るための便利なインターフェイスとして提供されている「bufio」の「Scanner」を使っています。

ファイルの書きこみサンプル

上記に、ファイルへの書き出しを加えてみます。

複数のファイルを一つのテキストファイルに追加書き込みするサンプルにしました。

import (
	"bufio"
	"fmt"
	"os"
	"path/filepath"
)

func interfile(srcpath string, distpath string) bool {
	var err error
	var fileOut *os.File

	fileIn, err := os.Open(srcpath)
	if err != nil {
		return false
	}
	defer fileIn.Close()

	fileOut, err = os.OpenFile(distpath, os.O_CREATE|os.O_APPEND|os.O_RDWR, 0666)

	if err != nil {
		return false
	}
	defer fileOut.Close()

	scanner := bufio.NewScanner(fileIn)
	writter := bufio.NewWriter(fileOut)
	for scanner.Scan() {
		writter.WriteString(scanner.Text() + "\n")
	}
	if err := scanner.Err(); err != nil {
		panic(err)
	}
	writter.Flush()
	return true
}

ポイントを補足します。 

追加書き込みする場合は「os.O_APPEND」を指定してファイルを開く必要があります。

なければ、作成もするので。

fileOut, err = os.OpenFile(distpath, os.O_CREATE|os.O_APPEND|os.O_RDWR, 0666)

となってます。

追加書き込みの必要がなければ

fileOut = os.Create(distpath)

でもいいですし。

fileOut, err = os.OpenFile(distpath, os.O_CREATE|os.O_RDWR, 0666)

でもいいです。

Windowsだけ使っていると違和感があるのが、OpenFileの最後の「0666」です。

これは新規にファイルを作成するときの「Mode」を示すのですが、UNIX系OSの仕様で下3桁が左から「所有者の権限」「グループの権限」「その他ユーザの権限」を表していて、それぞれの数字の意味を簡単に書くと。

  •  0    読み込み書き込み不可
  •  1    実行のみ
  •  2    書き込みのみ
  •  3    書き込み実行のみ
  •  4    読み込みのみ
  •  5    読み込み実行のみ
  •  6    読み込み書き込みのみ
  •  7    読み込み書き込み実行ができる

ですので、上記だと「666」なので、すべてのユーザに対して「読み込みと書き込み」の権限を与える・・みたいな意味になります。

あとは、「bufio」の「Writter」を使い、「WriteString」で文字列をファイルに書き込んでいて、最後にバッファのデータを書き出す「writter.Flush()」を呼ぶお決まりの処理をやっています。

まとめ

ファイルの読み・書きのサンプルは関数化しています。

WalkDirの無名関数の中で、例えば以下のように使う想定です。

if info.IsDir() {
           // フォルダーなので何もしない
        } else {
            if filepath.Ext(path) == ".go" {
                cat(path)
                interfile(path, "tmp.txt")
        }
 }

これを基本形として、変化させていけばいろいろできそうな気がします。

ではでは。