Tech Blog - mixross

Goでファイル名の中の数字を0埋め

このエントリーをはてなブックマークに追加
LINE

ファイル名が

prefix_9.jpg
prefix_10.jpg
prefix_11.jpg

のように数字部分が0埋めされておらず文字数がマチマチになると、lsコマンドでファイル名の昇順で確認すると

prefix_10.jpg
prefix_11.jpg
prefix_9.jpg

のように並んでしまう・・・。これを数値の順番に並ぶように数値部分の桁数を揃えたい。
(MacのFinderはここらへんをよしなにやってくれて9、10、11の順番で並べてくれたりもする。。)

そこで、Goの勉強がてら以下の仕様でコマンドツールを作ってみた。

  • パラメータでディレクトリと数値部分の桁数を指定
  • 指定ディレクトリ直下のファイルのみを対象に、拡張子の直前に数字がある場合のみ指定された桁数まで0を埋める

ソース

package main

import (
	"flag"
	"fmt"
	"strconv"
	"os"
	"path/filepath"
	"io/ioutil"
	"sync"
	"regexp"
)

func main() {
	// parameter
	flag.Parse()
	args := flag.Args()

	if len(args) != 2 {
		fmt.Println("parameter needs directory path and digit")
		return
	}

	// dicrectory path
	dir := args[0]
	_, err := os.Stat(dir)
	if err != nil {
		fmt.Println("directory path is not exist")
		return
	}

	// digit
	digit, err := strconv.Atoi(args[1])
	if err != nil {
		fmt.Println("digit is not number")
		return
	}

	if digit < 2 {
		fmt.Println("digit is required 2 or more")
		return
	}

	// get files
	files, err := ioutil.ReadDir(dir)
	if err != nil {
		fmt.Println(err.Error())
		return
	}

	wg := new(sync.WaitGroup)

	// change file name
	for _, file := range files {
		path := filepath.Join(dir, file.Name())
		if f, err := os.Stat(path); os.IsNotExist(err) || f.IsDir() {
			continue
		}

		wg.Add(1)

		// action
		go func(wg *sync.WaitGroup, name string) {
			changeName(dir, name, digit)

			wg.Done()
		}(wg, file.Name())
	}

	wg.Wait()
}

func changeName(dir string, name string, digit int) {
	rex := regexp.MustCompile(`^(.*?)(\d+)(\..+)$`)
	if rex.FindString(name) == "" {
		return
	}
	groups := rex.FindStringSubmatch(name)

	if len(groups[2]) >= digit {
		return
	}

	srcInt, _ := strconv.Atoi(groups[2])

	groups[2] = fmt.Sprintf("%0" + strconv.Itoa(digit) + "d", srcInt)

	newName := groups[1] + groups[2] + groups[3]

	if err := os.Rename(filepath.Join(dir, name), filepath.Join(dir, newName)); err != nil {
		fmt.Println(err)
		return
	}
}

コマンドライン引数

flag.Parse()

したあとに

args := flag.Args()

とすると []string でパラメータが取得できる。

goroutineの待機

main関数が終了するとgoroutineが実行されないので、main関数の最後でgoroutineの処理を待つ必要がある。

  • goroutineを呼び出す前にsync.WaitGroupを生成
    wg := new(sync.WaitGroup)
  • goroutineの終了を待ち受ける
    wg.Wait()
  • goroutineの実行数を設定(インクリメント)
    wg.Add(1)
  • goroutine内で処理終了を宣言
    wg.Done()

正規表現

rex := regexp.MustCompile(`正規表現パターン`)

で正規表現の定義を行い、

マッチした部分文字列を得る場合は

ret := rex.FindString(str)

マッチしなかった場合は空文字が返る。

正規表現のグループ化した文字列を取得する場合は

groups := rex.FindStringSubmatch(name)
  • groups[0]:マッチした全体の文字列
  • groups[n]:n番目のグループの文字列
RSS