新しいことにはウェルカム

技術 | 電子工作 | ガジェット | ゲーム のメモ書き

Go言語の正規表現・文字列操作方法メモ

Go言語は、シンプルな関数を組み合わせてプログラミングをすることが多く、また、関数毎にエラーチェックが発生するので、全般的にコードが長くなりがちです。

ちょっとした事をしたい時でも、コードをそれなりに書く必要があるのですが、毎回ゼロから書くのは面倒なので、よくやる処理を自分コピペ用にメモしておこうと思います。

ここでは、Go言語で、正規表現や文字列操作する方法の自分用メモを記載しました。

正規表現

まずは下記の一番凝ったやつから記します。

「全てのマッチ箇所を取得かつ、サブマッチあり」

  • regext.MustCompile()で正規表現オブジェクトを作成します
  • Regexp.FindAllStringSubmatch()で、全てのマッチ箇所とサブマッチ文字列を取得します
  • 1つのマッチ結果は[]stringに格納され、インデックス0が全体のマッチ箇所。1以降がサブマッチ文字列になります
  • 全てのマッチ結果を取得するので、結果は1つのマッチ結果([]string)のスライス[][]stringで返ります
  • Regexp.FindAllStringSubmatch()の2番目の引数は、取得するマッチ数で、全部のマッチ箇所を取得する場合は-1とします
str := `
2020-01-01_test
# comment
2020-02-02_TEST
2020-03-03_TEST`

re := regexp.MustCompile(`(\d{4})-(\d{2})-(\d{2})_(TEST)`)

res := re.FindAllStringSubmatch(str, -1)

for _, v := range res {
    fmt.Println(v)
    // [2020-02-02_TEST 2020 02 02 TEST]
    // [2020-03-03_TEST 2020 03 03 TEST]
}

フラグ

検索フラグを使う時は、正規表現定義の中で「(?<flag>)」と直接記載します。

  • m
    • mフラグがセットされていない場合、^$は、入力文字列の頭と末を指します
    • mフラグをセットした場合、^$は、行頭と行末を指します
  • i
    • iフラグがセットされていない場合、大文字・小文字を区別します
    • iフラグをセットした場合、大文字・小文字を区別しません
str := `
2020-01-01_test
# comment
2020-02-02_TEST
2020-03-03_TEST`

re := regexp.MustCompile(`(?mi)(\d{4})-(\d{2})-(\d{2})_(TEST)$`)

res := re.FindAllStringSubmatch(str, -1)

for _, v := range res {
    fmt.Println(v)
    // [2020-01-01_test 2020 01 01 test]
    // [2020-02-02_TEST 2020 02 02 TEST]
    // [2020-03-03_TEST 2020 03 03 TEST]
}

置換

  • Regexp.ReplaceAllString()で置換します
  • マッチ文字列は$0$1...で表します
str := `2020-01-01_test`

re := regexp.MustCompile(`(\d{4})-(\d{2})-(\d{2}).+$`)

res := re.ReplaceAllString(str, "$1/$2/$3")

fmt.Println(res) // 2020/01/01

関数名の「All」「String」

関数名には「All」あり・なし、「String」あり・なしバージョンがあります。

例)FindStringSubmatch()FindAllSubmatch()など

All

最初にマッチした部分だけ取得なら「All」をなしにします。

String

Go言語の仕様上、[]bytestringに変換すると、[]byteからstringへ、データのコピーが行われます。また、逆の場合もデータのコピーが行われます。

データを[]byteで扱っている場合は、検索のために都度stringに変換しているとコピーが発生して効率が悪いので、「String」なしの関数を使えば、 入出力データをstringではなく[]byteで扱うようにできます。

マッチするかだけ確認

Regexp.MatchString()を使えばマッチの確認だけ行えます

str := "TEST"

re := regexp.MustCompile(`(?i)test`)

res := re.MatchString(str)

fmt.Println(res) // true

文字列を区切りで分割(Split)

str := `a, b; c| d e`

re := regexp.MustCompile(`[,;\|\s]+`)

res := re.Split(str, -1)

fmt.Println(res) // [a b c d e]

文字列操作(strings)

stringsパッケージを使った文字列操作もよくやるのでまとめておきます。

文字列からインデックスで文字を取り出す

string[]byteでデータを保持しています。

stringをインデックスでアクセスすると、指定位置の文字ではなく、指定位置のbyteのデータを指します。

また、stringlen()は、文字数ではなくバイト数を返します。

string[]byteからインデックスで文字を取り出すには、一旦キャストで文字(rune)に分解すると楽です。

str := "日本語"

runes := []rune(str)

for i := 0; i < len(runes); i++ {
    fmt.Println(string(runes[i]), " --- ", string(runes[i:]))
    // 日  ---  日本語
    // 本  ---  本語
    // 語  ---  語
}

ただし、stringから文字(rune)にキャストすると、文字列データのコピーが発生します。[]runeからstringの変換もデータのコピーが発生します。

前後の空白を取り除く

strings.TrimSpace()を使うと、文字列の前後の空白を削除してくれます。空白は全角スペースやタブも空白とみなされます。

str := " \t TEST "
fmt.Println(strings.TrimSpace(str)) // TEST

大文字・小文字変換

str := "TEST"
fmt.Println(strings.ToLower(str)) // test

str = "test"
fmt.Println(strings.ToUpper(str)) // TEST

文字列を含むかチェック

正規表現を使うほどではないけど、ある文字列が、ある文字列を含むかちょっと調べたい時は、strings.Index()を使うと楽です。

strings.Index()はマッチする位置を返し、マッチしない場合は-1を返すので、-1かどうかを見て、文字列を含むかを判定することができます。

str := "TEST"
fmt.Println(strings.Index(str, "E")) // 1
fmt.Println(strings.Index(str, "e")) // -1

結合

strings.Join()で文字列の結合ができます。

chars := []string{"T", "E", "S", "T"}
fmt.Println(strings.Join(chars, ",")) // T,E,S,T

感想など

Go言語はC言語と違って、文字列はイミュータブル(変更不可)であるのが保証されているので、データにアクセスできるスライスへのキャストや、スライスから文字列へのキャストは、 必ずデータのコピーが発生するので注意が必要ですね。