初歩のGo言語
やってみます。


Hello World
最も簡単なHello Worldです。

package main
import (
  "fmt"
)
func main(){
  fmt.Println("Hello, Go言語 ワールド!")
}


UTF-8テキストファイルの読み書き

package main
import (
  "fmt"
  "io/ioutil"
  "os"
)

func main(){
  content, err := ioutil.ReadFile("./indata_utf8.txt")
  if err != nil {
    fmt.Println("Error:", err)
    os.Exit(1)
  }
  fmt.Println(string(content))

  err = ioutil.WriteFile("./outdata.txt", content, 0644)
  if err != nil {
    fmt.Println("Error:", err)
    os.Exit(1)
  }
}


シフトJISのテキストファイルの読み書き
SJISのファイルの読み書きは一気に複雑になります。なんでと言われてもしょうがない。

package main
import (
  "fmt"
  "log"
  "io/ioutil"
  "golang.org/x/text/encoding/japanese"
  "golang.org/x/text/transform"
)

func main(){
  s1, err := ioutil.ReadFile("./indata_sjis.txt")
  if err != nil { log.Fatal(err) }
  // s1はbyte[]なので、string()で変換する必要がある
  s2, n, err := transform.String(japanese.ShiftJIS.NewDecoder(), string(s1))
  if err != nil { log.Fatal(err) }
  fmt.Printf("%d bytes\n", n)

  fmt.Println(s2)

  s3 := `------
テストの
出力
です。
------
` + s2
  s4, n, err := transform.String(japanese.ShiftJIS.NewEncoder(), s3)
  if err != nil { log.Fatal(err) }
  fmt.Printf("%d bytes\n", n)

  // s4はstringなので、byte[]で変換する必要がある
  errw := ioutil.WriteFile("./outdata.txt", []byte(s4), 0644)
  if errw != nil { log.Fatal(errw) }
}


簡単なWebサーバ
まずプログラムを次のように書きます。

package main

import(
  "fmt"
  "os"
  "net/http"
  "html/template"
  "io/ioutil"
)

func main(){
    http.Handle("/", http.HandlerFunc(makePage))
    http.ListenAndServe(":9090", nil)
}

func makePage(w http.ResponseWriter, req *http.Request){
    content, err := ioutil.ReadFile("indexhtml.txt")
    if err != nil {
        fmt.Println("テンプレートファイル読み込み失敗", err)
        os.Exit(1)
    }
    templateStr := string(content)
    t := template.Must(template.New("sendname").Parse(templateStr))
    t.Execute(w, req.FormValue("yourname"))
}

このテンプレートファイル "indexhtml.txt" は次のように書きます。

<!DOCTYPE html>
<html lang="ja">
<head><meta charset="UTF-8"></head>
<body>
こんにちは。
{{if .}}
  {{ . }} さん
{{else}}
すみません、どちらさまですか
{{end}}

<form action="/" name="form1" method="GET">
<input type="text" size="40" name="yourname" value="" title="名前を入力してください">
<input type="submit" value="送信">
</form>
</body>
</html>


Excelファイルを読む

package main

import(
  "fmt"
  "log"
  "github.com/xuri/excelize/v2"
)

func main(){
    // ファイルはWriteロックされるがReadロックはされない
    // (他のプログラムが開いているExcelファイルでもOpenFileは成功する)
    f, err := excelize.OpenFile("./indata.xlsx")
    if err != nil { log.Fatal(err) }
    defer f.Close()

    // モジュールの関数はすべてシートの名前を渡す必要があるので、
    // 事前に分からなければシート番号から取得しておく必要がある
    // シート番号は0から始まる。1からではないので注意
    sheetName := f.GetSheetName(0)
    fmt.Println("Sheet Name:" + sheetName)

    // C18セルの値を出力
    cell, err := f.GetCellValue(sheetName, "C18")
    if err != nil { log.Fatal(err) }
    fmt.Println(cell)

    // セルを(列番号、行番号)で参照する。(行、列)ではないので注意
    // また番号は1から開始するので、(7,21)はG21セルの意味になる
    cname, err := excelize.CoordinatesToCellName(7, 21)
    if err != nil { log.Fatal(err) }
    cell, err = f.GetCellValue(sheetName, cname)
    if err != nil { log.Fatal(err) }
    fmt.Println(cell)

    // 全ての行を取得する。特定の行だけを取得する関数はないらしい
    rows, err := f.GetRows(sheetName)
    if err != nil { log.Fatal(err) }
    for y, row := range rows {
        if len(row) > 2 && row[2] == "本社" {
            fmt.Printf("見つかりました。%d行目\n", y)
            fmt.Println("------")
            for _, colcell := range row {
                fmt.Print(colcell + "\t")
            }
            fmt.Println("------")
            break // 見つけたら即ブレーク
        }
    }
}


Excelファイルに書く

package main

import(
  "log"
  "github.com/xuri/excelize/v2"
)

func main(){
    f := excelize.NewFile()
    // NewFile()でSheet1というシートが既に作られている。
    // さらにシートを増やすときにNewSheet()を使う
    f.NewSheet("新しいシート")

    f.SetCellValue("新しいシート", "B3", "Hello World")
    f.SetCellValue("新しいシート", "B4", 1234)
    f.SetCellValue("新しいシート", "B5", "こんにちは")

    err := f.SaveAs("./output/newbook.xlsx")
    if err != nil { log.Fatal(err) }
}


Excelの計算式を使う

package main

import(
  "log"
  "github.com/xuri/excelize/v2"
)

func main(){
    f := excelize.NewFile()

    f.SetCellValue("Sheet1", "B3", 1234)
    f.SetCellValue("Sheet1", "B4", 2345)
    f.SetCellValue("Sheet1", "B5", 3456)

    err := f.SetCellFormula("Sheet1", "C3", "=B3+B4")
    if err != nil { log.Fatal(err) }
    err = f.SetCellFormula("Sheet1", "C4", "=B4+B5")
    if err != nil { log.Fatal(err) }
    err = f.SetCellFormula("Sheet1", "C5", "=B4+B5")
    if err != nil { log.Fatal(err) }

    // 参照先のセルの値を変えてみる
    f.SetCellValue("Sheet1", "B5", 3457)

    err = f.SaveAs("newbook.xlsx")
    if err != nil { log.Fatal(err) }
}


Excelのスタイルのサンプル

package main

import(
  "time"
  "log"
  "github.com/xuri/excelize/v2"
)

func main(){
    f := excelize.NewFile()

    // 列幅と行の高さの設定。列幅は複数を一括して指定可能
    f.SetColWidth("Sheet1", "I", "K", 20)
    f.SetRowHeight("Sheet1", 3, 50)

    f.SetCellValue("Sheet1", "B3", "Hello World")
    f.SetCellValue("Sheet1", "B4", 1234)
    f.SetCellValue("Sheet1", "B5", "こんにちは")

    // 枠線の設定
    style, err := f.NewStyle(&excelize.Style {
      Border: []excelize.Border {
        {Type: "left",   Color: "0000FF", Style: 3},
        {Type: "right",  Color: "00FF00", Style: 4},
        {Type: "top",    Color: "FF0000", Style: 5},
        {Type: "bottom", Color: "FF00FF", Style: 6},
      },
    })
    if err != nil { log.Fatal(err) }
    err = f.SetCellStyle("Sheet1", "B3", "E5", style)

    // 塗りつぶし
    // 色が1要素なのに配列で指定するのはグラデーションにも対応しているから
    style, err = f.NewStyle(&excelize.Style {
      Fill: excelize.Fill{Type: "pattern", Color:[]string{"FFCCCC"}, Pattern:1},
    })
    if err != nil { log.Fatal(err) }
    err = f.SetCellStyle("Sheet1", "F6", "I8", style)

    // 日付の書式
    datestr := "2022/05/28 09:10:11"

    // Goの文字列型で日付を設定してしまうと、
    // Excelの書式を何に設定しても文字列として表示されてしまう
    f.SetCellValue("Sheet1", "J4", datestr)
    f.SetCellValue("Sheet1", "J5", datestr)
    f.SetCellValue("Sheet1", "J6", datestr)

    // GoのDateオブジェクトで設定すると、設定したExcel書式で表示される
    loc,_ := time.LoadLocation("Asia/Tokyo")
    d := time.Date(2022, 5, 28, 9, 10, 11, 0, loc)
    f.SetCellValue("Sheet1", "K4", d)
    f.SetCellValue("Sheet1", "K5", d)
    f.SetCellValue("Sheet1", "K6", d)

    // 組み込み書式を使用
    style, err = f.NewStyle(&excelize.Style {NumFmt: 15})
    if err != nil { log.Fatal(err) }
    err = f.SetCellStyle("Sheet1", "J5", "K5", style)

    // カスタム書式を使用
    // CustomNumFmtは*string型なので一旦変数に入れて&で参照する必要がある
    // ちなみにExcelでは月のmmと分のmmを位置関係だけで区別しているらしい
    expression := "yyyy/mm/dd hh:mm:ss"
    style, err = f.NewStyle(&excelize.Style {CustomNumFmt: &expression})
    if err != nil { log.Fatal(err) }
    err = f.SetCellStyle("Sheet1", "J6", "K6", style)

    err = f.SaveAs("newbook.xlsx")
    if err != nil { log.Fatal(err) }
}


構造体のサンプル

package main

import(
  "fmt"
)

// 各フィールドをカンマなどで区切る必要はない
type person struct {
    name string
    age  int
}

// 構造体の中に別の構造体名だけを書くと、継承っぽい記述ができる
// (fighter.person.nameをfighter.nameとも書ける)
// 「person person」と書いてしまうとこういう記述はできないので注意
type fighter struct {
    person
    hp    int
    maxHp int
    mp    int
    maxMp int
}

// 構造体のフィールド値を変更する場合は参照渡しにする必要がある
func (f *fighter) GetDamage(d int){
    f.hp -= d
    if f.hp < 0 { f.hp = 0 }
}

// これらはフィールドの値を変更しないので、参照渡しは必須ではないが一応統一する
func (p *person) PrintInfo(){
    fmt.Printf("%s (%d)\n", p.name, p.age)
}

func (f *fighter) PrintStatus(){
    fmt.Printf("%s (%d) %d/%d %d/%d\n", f.name, f.age, f.hp, f.maxHp, f.mp, f.maxMp)
}

// コンストラクタっぽいもの
func NewFighter(name string, age int, maxHp int, maxMp int) fighter {
    p := person{ name:name, age:age }
    f := fighter{ person: p, maxHp: maxHp, maxMp: maxMp }
    f.hp = f.maxHp
    f.mp = f.maxMp
    return f
}

func main(){
    f := NewFighter("Taro", 25, 50, 8)
    f.PrintInfo()
    f.PrintStatus()
    f.GetDamage(14)
    f.PrintStatus()
}


H2データベースに接続する
最初に以下を実行します。
go get github.com/jmrobles/h2go

H2はServerモードで起動しておく必要があります。
java -cp C:\Wintools\h2\bin\h2-2.2.220.jar org.h2.tools.Server -tcp -ifExists

package main

import (
    "database/sql"
    "log"
    _ "github.com/jmrobles/h2go"
)

func main() {
    // ファイルモード(Embeddedモード)では接続できない
    //db, err := sql.Open("h2", "stuser:stpass@h2:file://C:/usr/dbms/h2/shiten/stdb")
    // 本来のH2のURLではServerを起動した位置からの相対パスでもDBを指定できるはずですが、
    // この拡張の場合絶対パスしか指定できないらしい。さらに":"文字がエラーになるので、ドライブも指定できない
    db, err := sql.Open("h2", "h2://testuser:testpass@localhost/usr/dbms/h2/shiten/stdb")
    if err != nil {
        log.Fatalf("Can't connet to H2 Database: %s", err)
    }
    defer db.Close()

    rows, err := db.Query("SELECT SHITEN_CODE, SHITEN_NAME FROM SHITEN ORDER BY SHITEN_CODE")
    if err != nil {
        log.Printf("Query error: %s", err)
        return
    }
    defer func() {
        err = rows.Close()
        if err != nil {
            log.Printf("Can't close dataset: %s", err)
        }
    }()

    for rows.Next() {
        var shitenCode string
        var shitenName string

        rows.Scan(&shitenCode, &shitenName)
        log.Printf("コード: %s,\t名称 : %s", shitenCode, shitenName)
    }
    log.Printf("Done!!")
}


SQLiteデータベースに接続する
最初に以下を実行します。
go get modernc.org/sqlite

package main

import (
    "database/sql"
    "log"
    _ "modernc.org/sqlite"
)

func main() {
    // DBファイルのパスが間違っているとなぜか"out of memory(14)"というエラーになるので注意
    dbFilePath := "C:/usr/dbms/sqlite3/stdb/stdb"
    db, err := sql.Open("sqlite", "file:" + dbFilePath + "?cache=shared")
    if err != nil {
        log.Fatalf("Can't connect to database: %s", err)
    }
    defer db.Close()

    rows, err := db.Query("SELECT SHITEN_CODE, SHITEN_NAME FROM SHITEN ORDER BY SHITEN_CODE")
    if err != nil {
        log.Printf("Query error: %s", err)
        return
    }
    defer func() {
        err = rows.Close()
        if err != nil {
            log.Printf("Can't close dataset: %s", err)
        }
    }()

    for rows.Next() {
        var shitenCode string
        var shitenName string

        rows.Scan(&shitenCode, &shitenName)
        log.Printf("コード: %s,\t名称 : %s", shitenCode, shitenName)
    }
    log.Printf("Done!!")
}

Misc. Topics Top

(first uploaded 2024/09/01 last updated 2024/09/01, URANO398)