使わなくなったパソコンにSambaを入れて、ファイル共有用のサーバを構築してみたでLAN内のパソコン用のファイルサーバを構築してしばらく経過した。


そろそろ重要なファイルが保管される様になってきたので、

サーバのバックアップをとらないと不安になってきたわけで、

バックアップの検討を始めた。


サーバのPCにもうひとつハードディスクを搭載してバックアップも良いけど、

やっぱり他のPCに保管しておきたくなるので、

LAN内の他のPCにバックアップするためのコードを書いてみることにした。


実行ファイル形式にしておけば、

OS起動時に実行することができるわけで、

Go言語で書いてみることにした。


ちなみにこの記事を書いている段階では、

まだバックアップの仕組みはできていないので過程のメモということで


とりあえず今回は同じPC間でのディレクトリ構造とファイルのコピーまでやってみる。


※今回やったこと

gorawディレクトリにあるディレクトリ構造とファイルをgobackディレクトリにコピーする




Go言語にはWalkという便利な関数がある。

このWalkはディレクトリを指定すると、再帰的にディレクトリとファイルを探してきてくれるため、

今回のバックアップですごく大事になる。


ということで、まずはWalkを試す。

func Walk | filepath - The Go Programming Language


調べるディレクトリの構造は下記の通り


saito@saito-U37VC:~ tree goraw
goraw
├── a
│   ├── a-a
│   │   ├── a-a-a
│   │   │   └── hoge.txt
│   │   └── huga.txt
│   ├── a-b
│   │   └── a-b-a
│   └── a-c
├── b
│   ├── b-b
│   │   └── sample.png
│   └── sample.ods
└── c
    └── aaa.txt

9 directories, 5 files

backup.goというファイルを作成し、下記のコードでWalkを試す。

Walkはディレクトリを調べた後にCloseをしなくても良いっぽい


package main

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

func main() {
	srcdir := "/home/saito/goraw/"

	err := filepath.Walk(srcdir, func(path string, info os.FileInfo, err error) error {
		if err != nil {
			log.Println(err)
		}

		//ディレクトリかどうかを調べる
		if info.IsDir() {
			fmt.Println(path + "はディレクトリです")

		} else {
			fmt.Println(path + "はファイルです")
		}

		return nil
	})

	if err != nil {
		log.Println(err)
	}
}

コードを保存して実行してみると


saito@saito-U37VC:~ go run backup2.go
/home/saito/goraw/はディレクトリです
/home/saito/goraw/aはディレクトリです
/home/saito/goraw/a/a-aはディレクトリです
/home/saito/goraw/a/a-a/a-a-aはディレクトリです
/home/saito/goraw/a/a-a/a-a-a/hoge.txtはファイルです
/home/saito/goraw/a/a-a/huga.txtはファイルです
/home/saito/goraw/a/a-bはディレクトリです
/home/saito/goraw/a/a-b/a-b-aはディレクトリです
/home/saito/goraw/a/a-cはディレクトリです
/home/saito/goraw/bはディレクトリです
/home/saito/goraw/b/b-bはディレクトリです
/home/saito/goraw/b/b-b/hukuyou_wakime2.pngはファイルです
/home/saito/goraw/b/sample.odsはファイルです
/home/saito/goraw/cはディレクトリです
/home/saito/goraw/c/aaa.txtはファイルです

とりあえず、再帰的にディレクトリとファイルを調べてくれたことは確認できた。




次はバックアップ先のディレクトリ内にディレクトリ構造をコピーするコードを追加してみる。

Statにパスを指定すると、ファイルやディレクトリの構造を調べてくれる関数があるので利用する。

func Stat | os - The Go Programming Language


ディレクトリがなければ、Mkdirでディレクトリを作成する。

func Mkdir | os - The Go Programming Language


package main

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

func main() {
	srcdir := "/home/saito/goraw/"
	backupDir := "/home/saito/goback/"

	var dir string //gorawディレクトリ以下のパスを保持するための変数

	err := filepath.Walk(srcdir, func(path string, info os.FileInfo, err error) error {
		if err != nil {
			log.Println(err)
		}

		//ディレクトリかどうかを調べる
		if info.IsDir() {
			fmt.Println(path + "はディレクトリです")

			//ディレクトリの場合は、バックアップの方にディレクトリがあるか調べて、なければ作る
			dir = strings.Replace(path, srcdir, "", 1)
			if _, err = os.Stat(backupDir + dir); err != nil {
				fmt.Println(backupDir + dir + "ディレクトリを作成します")
				err = os.Mkdir(backupDir+dir, 0777)
				if err != nil {
					log.Println(err)
				}
			}
		} else {
			fmt.Println(path + "はファイルです")
		}

		return nil
	})

	if err != nil {
		log.Println(err)
	}
}

太字で示した箇所が追加したコードです。

バックアップ先のディレクトリにバックアップ元にあるディレクトリがあるか調べて、なければ作成するという処理を追加しています。


このコードを保存して実行してみると、


saito@saito-U37VC:~ go run backup3.go
/home/saito/goraw/はディレクトリです
/home/saito/goraw/aはディレクトリです
/home/saito/goback/aディレクトリを作成します
/home/saito/goraw/a/a-aはディレクトリです
/home/saito/goback/a/a-aディレクトリを作成します
/home/saito/goraw/a/a-a/a-a-aはディレクトリです
/home/saito/goback/a/a-a/a-a-aディレクトリを作成します
/home/saito/goraw/a/a-a/a-a-a/hoge.txtはファイルです
/home/saito/goraw/a/a-a/huga.txtはファイルです
/home/saito/goraw/a/a-bはディレクトリです
/home/saito/goback/a/a-bディレクトリを作成します
/home/saito/goraw/a/a-b/a-b-aはディレクトリです
/home/saito/goback/a/a-b/a-b-aディレクトリを作成します
/home/saito/goraw/a/a-cはディレクトリです
/home/saito/goback/a/a-cディレクトリを作成します
/home/saito/goraw/bはディレクトリです
/home/saito/goback/bディレクトリを作成します
/home/saito/goraw/b/b-bはディレクトリです
/home/saito/goback/b/b-bディレクトリを作成します
/home/saito/goraw/b/b-b/sample.pngはファイルです
/home/saito/goraw/b/sample.odsはファイルです
/home/saito/goraw/cはディレクトリです
/home/saito/goback/cディレクトリを作成します
/home/saito/goraw/c/aaa.txtはファイルです

上記のように実行され、


saito@saito-U37VC:~ tree goback
goback
├── a
│   ├── a-a
│   │   └── a-a-a
│   ├── a-b
│   │   └── a-b-a
│   └── a-c
├── b
│   └── b-b
└── c

9 directories, 0 files

実際にtreeコマンドで調べてみても、ディレクトリ構造のコピーができている。




最後にファイルをコピーしてみる。

ファイルのコピーはコピー元のファイルをOpenで開き、コピーしたい箇所にCreateでファイルを作成しておき、最後にCopyでファイルの内容をコピーする。

func Create | os - The Go Programming Language

func Copy | os - The Go Programming Language


で、追加したコードは下記の通り

※自身が理解しやすい様に重複している処理があります


package main

import (
	"fmt"
	"io"
	"log"
	"os"
	"path/filepath"
	"strings"
)

func main() {
	srcdir := "/home/saito/goraw/"
	backupDir := "/home/saito/goback/"

	var dir string
	var fpath string

	var src *os.File
	var dst *os.File

	err := filepath.Walk(srcdir, func(path string, info os.FileInfo, err error) error {
		if err != nil {
			log.Println(err)
		}

		//ディレクトリかどうかを調べる
		if info.IsDir() {
			fmt.Println(path + "はディレクトリです")

			//ディレクトリの場合は、バックアップの方にディレクトリがあるか調べて、なければ作る
			dir = strings.Replace(path, srcdir, "", 1)
			if _, err = os.Stat(backupDir + dir); err != nil {
				fmt.Println(backupDir + dir + "ディレクトリを作成します")
				err = os.Mkdir(backupDir+dir, 0777)
				if err != nil {
					log.Println(err)
				}
			}
		} else {
			fmt.Println(path + "はファイルです")
			fpath = strings.Replace(path, srcdir, "", 1)

			//ファイルをコピーする
			fmt.Println(info.Name() + "をコピーします")
			src, err = os.Open(path)
			if err != nil {
				log.Println(err)
			}

			dst, err = os.Create(backupDir + fpath)
			if err != nil {
				log.Println(err)
			}

			if _, err = io.Copy(dst, src); err != nil {
				log.Println(err)
			}

			//今回はコピー用の関数を用意していないので、deferではなく直接Closeを指定
			src.Close()
			dst.Close()
		}

		return nil
	})

	if err != nil {
		log.Println(err)
	}
}

このコードを実行してみると


saito@saito-U37VC:~ go run backup.go
/home/saito/goraw/はディレクトリです
/home/saito/goraw/aはディレクトリです
/home/saito/goraw/a/a-aはディレクトリです
/home/saito/goraw/a/a-a/a-a-aはディレクトリです
/home/saito/goraw/a/a-a/a-a-a/hoge.txtはファイルです
hoge.txtをコピーします
/home/saito/goraw/a/a-a/huga.txtはファイルです
huga.txtをコピーします
/home/saito/goraw/a/a-bはディレクトリです
/home/saito/goraw/a/a-b/a-b-aはディレクトリです
/home/saito/goraw/a/a-cはディレクトリです
/home/saito/goraw/bはディレクトリです
/home/saito/goraw/b/b-bはディレクトリです
/home/saito/goraw/b/b-b/sample.pngはファイルです
sample.pngをコピーします
/home/saito/goraw/b/sample.odsはファイルです
sample.odsをコピーします
/home/saito/goraw/cはディレクトリです
/home/saito/goraw/c/aaa.txtはファイルです
aaa.txtをコピーします

実際のファイルシステムをtreeコマンドで確認してみると、


saito@saito-U37VC:~ tree goback
goback
├── a
│   ├── a-a
│   │   ├── a-a-a
│   │   │   └── hoge.txt
│   │   └── huga.txt
│   ├── a-b
│   │   └── a-b-a
│   └── a-c
├── b
│   ├── b-b
│   │   └── sample.png
│   └── sample.ods
└── c
    └── aaa.txt

ファイルも無事コピーされたみたいだ。

テキストファイルやそれ以外のファイルを開いても、特にデータが損傷しているということもなく、

コピー元と同じように開くことができた。


とりあえず、これで同じPC内であればバックアップができるようになったね。


ただし、

今回のコードでは、バックアップ先にファイルがあって、元のファイルに更新がなくても常にコピーされるという無駄な処理があるため、

ファイルの更新時刻に合わせてコピーを行うかどうかという処理は入れておきたい。


-続く-