Tech Blog - mixross
ファイル名が
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番目のグループの文字列