覚えたら書く

IT関係のデベロッパとして日々覚えたことを書き残したいです。twitter: @yyoshikaw

Go言語 - flagパッケージでコマンドラインパラメータを処理する

前日のエントリではos.Argsを用いてコマンドラインのパラメータを取り扱いました。

これ以外にも、Go言語ではflagパッケージというものが用意されています。
コマンドラインで与えられたパラメータのパース処理を簡単に実施できるようになっています。

本エントリでは、flagパッケージを利用したコマンドラインパラメータの基本的な取扱い方について確認してみます。


サンプルコマンドの仕様

本エントリで作成するコマンドの仕様は以下の通りとします

samplecmd.exe -m <メッセージ> -n <カウント>

または

samplecmd.exe -m <メッセージ> -n <カウント> -debug

で実行できる。

  • -debugを指定した場合にdebug=trueとなり、指定しない場合はdebug=false
  • <カウント>は0以上の値


上記コマンドをflagパッケージを使って2パターンで処理してみます


パターン1

string型で取り扱うパラメータに対して、flag.String(<パラメータ名>, <デフォルト値>, <パラメータの説明>)でパラメータの値を格納する変数のポインタを取得します。
uint型のパラメータではflag.Uint, bool値にいてはflag.Boolを利用して変数を宣言します。

各変数が宣言されたあとに、flag.Parseを実行することでコマンドラインのパラメータがパースされ、各変数に値が格納されます


■サンプルコード

変数の宣言とパラメータの取り扱い方を同時に定義するようなイメージになります

package main

import (
    "flag"
    "fmt"
)

func main() {
    var (
        msg   = flag.String("m", "default message.", "Message")
        num   = flag.Uint("n", 0, "Count(>= 0)")
        debug = flag.Bool("debug", false, "Debug Mode enabled?")
    )

    flag.Parse()

    fmt.Printf("param -m -> %s\n", *msg)
    fmt.Printf("param -n -> %d\n", *num)
    fmt.Printf("param -debug -> %t\n", *debug)
}


■実行結果

C:\go_tmp\samplecmd.exe -m Hello -n 100
param -m -> Hello
param -n -> 100
param -debug -> false


<パラメータ名>=<値>の指定も可能

C:\go_tmp\samplecmd.exe -m "Hello World" -debug -n=123
param -m -> Hello World
param -n -> 123
param -debug -> true


パターン2

パターン1と大きな差はありませんが、以下のような流れで処理します

パラメータの値を格納する変数を宣言します。

string型で取り扱うパラメータに対して、flag.StringVar(<値を格納する変数のアドレス>, <パラメータ名>, <デフォルト値>, <パラメータの説明>)を実行します。
uint型のパラメータではflag.UintVar, bool値にいてはflag.BoolVarを利用します。

その後に、flag.Parseを実行することでコマンドラインのパラメータがパースされ、各変数に値が格納されます


■サンプルコード

パラメータの値を格納する変数の宣言とパラメータの取り扱い方を別々に定義するようなイメージになります

package main

import (
    "flag"
    "fmt"
)

func main() {
    var (
        msg   string
        num   uint
        debug bool
    )

    flag.StringVar(&msg, "m", "default message.", "Message")
    flag.UintVar(&num, "n", 0, "Count(>= 0)")
    flag.BoolVar(&debug, "debug", false, "Debug Mode enabled?")

    flag.Parse()

    fmt.Printf("param -m -> %s\n", msg)
    fmt.Printf("param -n -> %d\n", num)
    fmt.Printf("param -debug -> %t\n", debug)
}


■実行結果

C:\go_tmp\samplecmd.exe -m sample_message -n 1
param -m -> sample_message
param -n -> 1
param -debug -> false


<パラメータ名>=<値>の指定も可能

C:\go_tmp\samplecmd.exe -m=sample_message2 -n 2 -debug
param -m -> sample_message2
param -n -> 2
param -debug -> true


ヘルプを表示する

<コマンド> -h または コマンド -help を実行するとコマンドのヘルプ(Usage)が表示されます

■実行結果

C:\go_tmp\samplecmd.exe -h
Usage of samplecmd.exe:
  -debug
        Debug Mode enabled?
  -m string
        Message (default "default message.")
  -n uint
        Count(>= 0)


間違った値を指定するとUsageが表示される

flagパッケージを使ってコマンドラインパラメータを処理する場合、コマンドラインで間違ったパラメータを指定するとusage(使い方が表示されます)


存在しないパラメータ名(-c)を指定した場合

■実行結果

C:\go_tmp\samplecmd.exe -m something -c 10
flag provided but not defined: -c
Usage of samplecmd.exe:
  -debug
        Debug Mode enabled?
  -m string
        Message (default "default message.")
  -n uint
        Count(>= 0)


パラメータの型に合わない値(-n にマイナス値)を指定した場合

■実行結果

C:\go_tmp\samplecmd.exe -m something -n -10
invalid value "-10" for flag -n: strconv.ParseUint: parsing "-10": invalid syntax
Usage of samplecmd.exe:
  -debug
        Debug Mode enabled?
  -m string
        Message (default "default message.")
  -n uint
        Count(>= 0)


デフォルト値の利用

パラメータに値を指定しない場合はデフォルト値が利用されます。

以下例では、-m を省略しているので、デフォルト値(default message.)が変数に格納されている

■実行結果

C:\go_tmp\samplecmd.exe -m something -n 10
param -m -> default message.
param -n -> 10
param -debug -> false



関連エントリ

Go言語 - os.Argsでコマンドラインパラメータを受け取る

コマンドラインで与えられたパラメータはosパッケージのArgsで受け取ることができます。
C言語のmain関数やJavaのmainメソッドでは引数でコマンドラインパラメータを受け取りますが、Golangでは引数ではなくos.Argsを通じて取得することになります

os.Argsは以下の特徴を持ちます

  • os.Argsはstring型のスライス
  • 0番目の要素には実行したコマンド名が格納される
  • 1番目以降の各要素にコマンドに渡された各引数が格納される


以下でos.Argsを利用するプログラムを作成して、実際にコマンドとして実行して動きを確認してみます


■サンプルコード

os.Argsの要素数と中の値を表示するプログラムになっています

package main

import (
    "fmt"
    "os"
)

func main() {
    fmt.Printf("args count: %d\n", len(os.Args))
    fmt.Printf("args : %#v\n", os.Args)

    for i, v := range os.Args {
        fmt.Printf("args[%d] -> %s\n", i, v)
    }
}


■実行結果

○引数として param1 と param2 を与える

C:\go_tmp\osargstrial.exe param1 param2
args count: 3
args : []string{"osargstrial.exe", "param1", "param2"}
args[0] -> osargstrial.exe
args[1] -> param1
args[2] -> param2

先頭要素にはコマンド名が格納され、それ以降の要素に引数の値が格納されています


○引数として param1 と param2 と "aaaa bbbb" と -version を与える

C:\go_tmp\osargstrial.exe param1 param2 "aaaa bbbb" -version
args count: 5
args : []string{"osargstrial.exe", "param1", "param2", "aaaa bbbb", "-version"}
args[0] -> osargstrial.exe
args[1] -> param1
args[2] -> param2
args[3] -> aaaa bbbb
args[4] -> -version

"(ダブルクォート)で囲った引数は、os.Argsの1要素で扱われます


○引数なし

C:\go_tmp\osargstrial>osargstrial.exe
args count: 1
args : []string{"osargstrial.exe"}
args[0] -> osargstrial.exe

引数無しコマンドが実行された場合は、os.Argsにはコマンド名のみが格納されます


補足

os.Argsでコマンドラインパラメータを扱うことはできますが、もう少し高機能なflagパッケージというものも提供されいます。
flagパッケージの使い方については以下エントリを参照ください。

Go言語 - Tickerを使った定期処理

gorutinetimeパッケージのTickerを使ってバックグラウンドで定期処理を実行することができます。

本サンプルでは、1秒間隔のTickerを生成して、現在時刻を表示します。
5秒後にTicker停止用のメッセージがチャネルに送られて、Tickerをstopしています


■サンプルコード

package main

import (
    "fmt"
    "time"
)

func main() {
    chStop := make(chan int, 1)
    timerfunc(chStop)

    time.Sleep(time.Second * 5)
    chStop <- 0 // Tickerをstopさせるメッセージ

    close(chStop)

    time.Sleep(time.Second * 1)

    fmt.Println("Application End.")
}

func timerfunc(stopTimer chan int) {
    go func() {
        ticker := time.NewTicker(1 * time.Second) // 1秒間隔のTicker

    LOOP:
        for {
            select {
            case <-ticker.C:
                fmt.Printf("now -> %v\n", time.Now())
            case <-stopTimer:
                fmt.Println("Timer stop.")
                ticker.Stop()
                break LOOP
            }
        }
        fmt.Println("timerfunc end.")
    }()
}


■実行結果

now -> 2017-04-27 06:03:55.7592988 +0900 JST
now -> 2017-04-27 06:03:56.7592988 +0900 JST
now -> 2017-04-27 06:03:57.7592988 +0900 JST
now -> 2017-04-27 06:03:58.7592988 +0900 JST
now -> 2017-04-27 06:03:59.7592988 +0900 JST
Timer stop.
timerfunc end.
Application End.

というわけで1秒間隔で現在時刻が表示される処理ができました

Go言語 - 外部コマンドを実行する

Go言語で外部コマンドを実行するには、execパッケージを利用します。(実際のimportはimport "os/exec"となります)

実行したいコマンドをexec.Command(<コマンド>)で生成して、生成したコマンドをCmd.RunCmd.Outputで実行する流れになります


Run - コマンドの完了を待つ

Cmd.Runを使うと実行したコマンドの完了を待ちます。ただし、コマンドが出力した結果などは受け取ることはできません

■サンプルコード

package main

import (
    "fmt"
    "os/exec"
)

func main() {
    err := exec.Command("C:/app/SampleApp.exe").Run()
    if err != nil {
        fmt.Println("Command Exec Error.")
    }
}


Output - コマンドの完了を待ち出力された結果も取得する

Cmd.Outputを使うと実行したコマンドが標準出力に出力した内容を戻り値で取得することができます

■サンプルコード

package main

import (
    "fmt"
    "os/exec"
)

func main() {
    out, err := exec.Command("ls", "-la").Output()
    if err != nil {
        fmt.Println("Command Exec Error.")
    }

    // 実行したコマンドの結果を出力
    fmt.Printf("ls result: \n%s", string(out))
}

■実行結果

ls result: 
total 32
drwxr-xr-x  6 user1 Administrators  4096 Apr 16 16:55 .
drwxr-xr-x 44 user1 Administrators 16384 Apr 15 09:34 ..
drwxr-xr-x  2 user1 Administrators     0 Apr 16 16:23 img
drwxr-xr-x 10 user1 Administrators  4096 Apr 16 19:25 sampleapp


ただし、Cmd.Outputを使った場合は標準エラー出力の内容は取得できません。
例えば存在しないディレクトリ指定でlsを実行すると標準エラー出力にエラー内容が出力されますが、それを取得することができません

■サンプルコード

package main

import (
    "fmt"
    "os/exec"
)

func main() {
    out, err := exec.Command("ls", "noexists").Output()
    if err != nil {
        fmt.Println("Command Exec Error.")
    }

    // 実行したコマンドの標準出力の内容
    fmt.Printf("ls result: \n%s", string(out))
}

■実行結果

ls result: 

実行結果の通り、標準エラー出力の内容を拾えていません。これを解決するには Cmd.CombinedOutputを使用します


CombinedOutput - 標準出力も標準エラー出力も取得する

Cmd.CombinedOutputを使うと実行したコマンドが標準出力と標準エラー出力に出力した内容を戻り値で取得することができます

■サンプルコード

package main

import (
    "fmt"
    "os/exec"
)

func main() {
    out, err := exec.Command("ls", "noexists").CombinedOutput()
    if err != nil {
        fmt.Println("Command Exec Error.")
    }

    // 実行したコマンドの標準出力+標準エラー出力の内容
    fmt.Printf("ls result: \n%s", string(out))
}

■実行結果

fmt.Println("Command Exec Error.")
ls result: 
ls: noexists: No such file or directory

標準エラー出力の内容を取得することができています


Start - コマンドの完了を待たない

Cmd.Startを実行するとコマンドの完了を待ちません。ただし、Cmd.Waitを組み合わせることで待ち合わせることもできます

■サンプルコード

package main

import (
    "fmt"
    "os/exec"
)

func main() {
    cmd := exec.Command("uname")

    fmt.Println("Command Start.")

    err := cmd.Start()
    if err != nil {
        fmt.Println("Command Exec Error.")
    }

    cmd.Wait()

    fmt.Println("Command Exit.")
}

■実行結果

Command Start.
Command Exit.

Go言語 - 整数型

Go言語では整数を扱うための型が複数提供されています。
以下は、各整数型の違いなどについてのメモとなります。


実装系に依存しない整数型

以下の整数型は実装系によらずサイズが決まっています

サイズ(bit) 符号 最小値 最大値
int8 8 あり -128 127
int16 16 あり -32768 32767
int32 32 あり -2147483648 2147483647
int64 64 あり -9223372036854775808 9223372036854775807
uint8(byte) 8 なし 0 255
uint16 16 なし 0 65535
uint32 32 なし 0 4294967295
uint64 64 なし 0 18446744073709551615

uint8の別名としてbyte型が定義されています。1バイト単位を表現するような処理の記述にはbyte型の方が向いているかもしれないです


mathパッケージのmath.MinXXXmath.MaxXXXの定数で各型で扱うことのできる最小値、最大値を確認できます。

package main

import (
    "fmt"
    "math"
)

func main() {
    fmt.Printf("int8   -> min:%d, max:%d\n", math.MinInt8, math.MaxInt8)
    fmt.Printf("int16  -> min:%d, max:%d\n", math.MinInt16, math.MaxInt16)
    fmt.Printf("int32  -> min:%d, max:%d\n", math.MinInt32, math.MaxInt32)
    fmt.Printf("int64  -> min:%d, max:%d\n", int64(math.MinInt64), int64(math.MaxInt64))

    fmt.Printf("uint8  -> min:0, max:%d\n", math.MaxUint8)          // uint8の最小値は0。最小値の定数は無い
    fmt.Printf("uint16 -> min:0, max:%d\n", math.MaxUint16)         // uint16の最小値は0。最小値の定数は無い
    fmt.Printf("uint32 -> min:0, max:%d\n", uint32(math.MaxUint32)) // uint32の最小値は0。最小値の定数は無い
    fmt.Printf("uint64 -> min:0, max:%d\n", uint64(math.MaxUint64)) // uint64の最小値は0。最小値の定数は無い
}

■実行結果

int8   -> min:-128, max:127
int16  -> min:-32768, max:32767
int32  -> min:-2147483648, max:2147483647
int64  -> min:-9223372036854775808, max:9223372036854775807
uint8  -> min:0, max:255
uint16 -> min:0, max:65535
uint32 -> min:0, max:4294967295
uint64 -> min:0, max:18446744073709551615


実装系依存の整数型

Go言語はサイズがきっちり決まった整数型だけではなく、OSやCPUなどの実装系に依存した(実装系によってサイズが変わる)整数型も存在しています。
(C言語におけるintに似たような感じです)

サイズ(32bit実装) サイズ(64bit実装) 符号
int 32bit 64bit あり
uint 32bit 64bit なし
uintptr なし

※uintprtは ポインタの値をそのまま格納するために十分な大きさの符号なし整数


変数宣言した値の各型の確認

整数リテラル含めた各型の値を変数宣言して、実際に変数の型がどうなっているかを確認してみます。

先に書きますが、整数リテラルを使って暗黙的に定義した変数はint型になります

■サンプルコード

package main

import "fmt"

func main() {
    val1 := 123
    fmt.Printf("val1 -> %d, type: %T\n", val1, val1)

    val2 := int(123)
    fmt.Printf("val2 -> %d, type: %T\n", val2, val2)

    val3 := uint(123)
    fmt.Printf("val3 -> %d, type: %T\n", val3, val3)

    val4 := int16(123)
    fmt.Printf("val4 -> %d, type: %T\n", val4, val4)

    val5 := int32(123)
    fmt.Printf("val5 -> %d, type: %T\n", val5, val5)

    val6 := int64(123)
    fmt.Printf("val6 -> %d, type: %T\n", val6, val6)
}

■実行結果

val1 -> 123, type: int
val2 -> 123, type: int
val3 -> 123, type: uint
val4 -> 123, type: int16
val5 -> 123, type: int32
val6 -> 123, type: int64

整数リテラルで宣言した変数(val1)の型がintになっていることが分かります