標準ライブラリ fmt に用意されているC言語ライクな書式付き入出力関数は、拙作のページ ファイル入出力 で簡単に紹介しました。今回はもう少しだけ詳しく書式付き入出力関数について説明します。
パッケージ fmt の Printf のように、データを整形して出力する関数のことを書式付き出力関数といいます。Printf 以外にも次の関数が用意されています。
func Printf(format string, a ...interface{}) (n int, err error) func Fprintf(w io.Writer, format string, a ...interface{}) (n int, err error) func Sprintf(format string, a ...interface{}) string
Printf は標準出力に、Fprintf は io.Writer に整形した結果を出力します。返り値 n は出力したバイト数で、エラーが発生した場合は返り値 err でエラーを報告します。Sprintf は整形した結果を文字列にして返します。
引数の文字列 format を書式文字列といい、出力に関する様々な指定を行います。書式文字列はそのまま文字列として扱われますが、文字列の途中にパーセント % が表れると、その後ろの文字を変換指示子として解釈し、引数に与えられたデータをその指示に従って表示します。Go 言語では % + 変換指示子 を verb といいます。
主な verb を以下に示します。
%v デフォルト %#v Go 言語の構文表現 %T データ型の構文表現 %% 記号 % を表す %t 真偽値 (true, false) %b, %d, %o, %x 整数 (2, 10, 8, 16 進数) %c 文字 (Unicode) %U 文字 (Unicode 形式, U+16進数) %e, %f, %g 浮動小数点数、複素数 %s 文字列 %p ポインタ
それから、% と変換指示子の間にオプションでいろいろな設定を行うことができます。
これらのオプションは verb によって動作が異なる場合があります。簡単な例を示しましょう。
リスト : 整数の出力 (fmt01.go) package main import "fmt" func main() { fmt.Printf("%d, %x, %o\n", 100, 100, 100) fmt.Printf("[%d]\n", 10) fmt.Printf("[%4d]\n", 10) fmt.Printf("[%4d]\n", 100000) fmt.Printf("[%4d]\n", 123456) fmt.Printf("[%-8d]\n", 123456) fmt.Printf("[%08d]\n", 123456) }
$ go run fmt01.go 100, 64, 144 [10] [ 10] [100000] [123456] [123456 ] [00123456]
% の次の文字 d, x, o が変換指示子です。これらの指示子は整数値を表示する働きをします。例が示すように、d は 10 進数、x は 16 進数、o は 8 進数で表示します。verb の個数と与えるデータの数が合わないとエラーになるので注意してください。記号 % を出力したい場合は %% と続けて書きます。
整数値を表示する verb は、データを表示するフィールド幅を指定することができます。最初の例がフィールド幅を指定しない場合で、次の例がフィールド幅を 4 に指定した場合です。10 ではフィールド幅に満たないので、右詰めに出力されています。もし、フィールド幅に収まらない場合は、指定を無視して数値を出力します。フィールド幅を 0 で埋めたい場合は、フラグに 0 を指定します。左詰めにしたい場合は、フラグに - を指定します。
%s は文字列を表示します。%s の場合でも、フィールド幅を指定することができます。簡単な例を示しましょう。
リスト : 文字列の表示 (fmt02.go) package main import "fmt" func main() { a := "hello, world"; fmt.Printf("[%s]\n", a); fmt.Printf("[%20s]\n", a); fmt.Printf("[%-20s]\n", a); }
$ go run fmt02.go [hello, world] [ hello, world] [hello, world ]
浮動小数点数や複素数を表示するには %e, %f, %g を使います。
小数点の右側に印字される桁数は、%e と %f ではデフォルトで 6 桁になります。これを変更するには精度を使います。精度はピリオド ( . ) のあとに数字を指定します。たとえば、"%.14f" とすると、小数点は 14 桁で表示されます。%g は最大有効桁数で表示します。%g で精度を指定すると有効桁数が変更されます。小数点数の桁ではないので注意してください。
簡単な例を示します。
リスト : 浮動小数点数の表示 (fmt03.go) package main import ( "fmt" "math" ) func main() { pi := math.Pi fmt.Printf("%e\n", pi) fmt.Printf("%.14e\n", pi) fmt.Printf("%f\n", pi) fmt.Printf("%.14f\n", pi) fmt.Printf("%g\n", pi) fmt.Printf("%.6g\n", pi) }
$ go run fmt03.go 3.141593e+00 3.14159265358979e+00 3.141593 3.14159265358979 3.141592653589793 3.14159
このほかにも、書式付き出力関数にはいろいろな機能があります。詳細は Go 言語のマニュアル パッケージ fmt をお読みください。
Go 言語の文字列は byte の配列 (スライス) として扱われます。Go 言語のプログラム (ソースファイル) は UTF-8 でエンコードすることが規定されているので、文字列の長さはバイト数と文字数で異なる場合があります。当然ですが、文字列に添字を適用すると byte が返されるので、文字単位で値を取り出すことはできません。この場合、for 文の range を使うと文字列から文字を取り出していくことができます。
簡単な例を示しましょう。
リスト : 文字列から文字を取り出す (char01.go) package main import "fmt" func main() { a := "hello, world" b := "こんにちは世界" fmt.Printf("len(a) = %d\n", len(a)) fmt.Printf("len(b) = %d\n", len(b)) for i := 0; i < len(a); i++ { fmt.Printf("%x ", a[i]) } fmt.Println("") for i := 0; i < len(b); i++ { fmt.Printf("%x ", b[i]) } fmt.Println("") for i, c := range a { fmt.Printf("%d: [%c]\n", i, c) } fmt.Println("") for i, c := range b { fmt.Printf("%d: [%c]\n", i, c) } fmt.Println("") }
$ go run char01.go len(a) = 12 len(b) = 21 68 65 6c 6c 6f 2c 20 77 6f 72 6c 64 e3 81 93 e3 82 93 e3 81 ab e3 81 a1 e3 81 af e4 b8 96 e7 95 8c 0: [h] 1: [e] 2: [l] 3: [l] 4: [o] 5: [,] 6: [ ] 7: [w] 8: [o] 9: [r] 10: [l] 11: [d] 0: [こ] 3: [ん] 6: [に] 9: [ち] 12: [は] 15: [世] 18: [界]
Go 言語の場合、文字を表すデータ型として rune が用意されています。rune は int32 の別名で、文字を表す Unicode の値になります。文字リテラルは 'a' や 'あ' のように ' で囲んで記述します。文字数は標準ライブラリ unicode/utf8 の関数 RuneCountInString で数えることができます。
func RuneCountInString(s string) (n int)
ファイルなどから文字単位でデータを読み込みたい場合は、標準ライブラリ bufio の関数 ReadRune を使うと便利です。
func (b *Reader) ReadRune() (r rune, size int, err error)
返り値 r が文字、size がバイト数、err がエラーを表します。
簡単な使用例と実行例を示します。
リスト : 文字単位での読み込み (char02.go) package main import ( "fmt" "io" "os" "bufio" ) func main() { rd := bufio.NewReader(os.Stdin) for { r, s, err := rd.ReadRune() if s == 0 { if err != io.EOF { fmt.Printf("oops! %v\n", err) } break } else { fmt.Printf("%c\n", r) } } }
$ go run char02.go hello, world h e l l o , w o r l d こんにちは世界 こ ん に ち は 世 界
ファイルからデータを読み込む場合、データをバッファに格納するだけではなく、数値や他のデータ型に変換できると便利です。Go 言語の場合、書式付き入力関数を使うと、入力データの変換を行うことができます。
func Scanf(format string, a ...interface{}) (n int, err error) func Fscanf(r io.Reader, format string, a ...interface{}) (n int, err error) func Sscanf(str string, format string, a ...interface{}) (n int, err error)
Scanf は標準入力 (os.Stdin)、Fscanf は io.Reader (r)、Sscanf は文字列 (str) からデータを読み込みます。返り値 n は読み込んだデータ数で、失敗した場合は返り値 err でエラーを報告します。書式文字列 format のあとの引数には、読み込んだデータを格納する変数をポインタで指定します。
簡単な例を示しましょう。
リスト : Sscanf の使用例 (fmt04.go) package main import "fmt" func main() { var n int var d float64 var s string fmt.Sscanf("5678abcd", "%d", &n) fmt.Printf("%d\n", n) fmt.Sscanf("5678abcd", "%o", &n) fmt.Printf("%o\n", n) fmt.Sscanf("5678abcd", "%x", &n) fmt.Printf("%x\n", n) fmt.Sscanf("1.23456789", "%f", &d) fmt.Printf("%f\n", d) fmt.Sscanf("hello, world", "%s", &s) fmt.Printf("%s\n", s) }
$ go run fmt04.go 5678 567 5678abcd 1.234568 hello,
%d はデータを 10 進整数に変換します。入力データが 5678abcd の場合、5678 まで読み込んで整数に変換します。このとき、入力データ abcd は残っていることに注意してください、%o は 8 進整数に、%x は 16 進整数に変換します。%f はデータを浮動小数点数 (float64) に変換します。%s は空白以外の文字列を読み込みます。空白文字 (タブや改行も含む) は区切り記号として使われます。
書式文字列の中では複数の verb を指定することができます。入力データの空白文字は区切り記号として扱われるので、"%d%d%d" は入力データ "123 456 789" とマッチングします。なお、先頭にある空白文字は読み飛ばされますが、読み飛ばさない変換指示子 (たとえば %c など) もあります。
それでは簡単な使用例として、ファイルに数値データがありそれを全部足し算するプログラムを作ってみましょう。次のリストを見てください。
リスト : 数値データの総和 (fmt05.go) package main import ( "fmt" "io" "os" ) func main() { a, c, n := 0, 0, 0 for { m, err := fmt.Scanf("%d", &n) if m == 0 { if err == io.EOF { fmt.Printf("入力数 %d, 合計 = %d\n", c, a) break } else { fmt.Printf("%d 番目のデータに誤りがあります: %v\n", c + 1, err) os.Exit(1) } } a += n c++ } }
書式文字列には区切り文字が指定されていませんが、先頭の空白文字は読み飛ばされるので問題ありません。ファイルが終了した場合、Scanf は 0 と io.EOF を返します。m と err をチェックしてファイルが終了したならば、入力されたデータ数 (変数 c) と合計値 (変数 a) を Printf で表示します。
err が io.EOF でない場合、データの読み込みに失敗しています。Printf でデータの番号とエラーの種類を表示して処理を終了します。それ以外の場合はデータを正常に読み込むことができました。a に n の値を加算して c の値をインクリメントします。
簡単な実行例を示します。
$ cat input.txt 28236 21625 22672 30646 8110 30976 $ go run fmt05.go < input.txt 入力数 6, 合計 = 142265 $ cat inputerr.txt 28236 21625 22672 30646 abcde 8110 30976 $ go run fmt05.go < inputerr.txt 5 番目のデータに誤りがあります: expected integer exit status 1
最後に簡単な例題として、ファイルのバイト数と行数と単語をカウントするプログラムを作ります。単語は空白文字で区切られた文字列とします。なお、Unix 系 OS には同等の機能を持つコマンド wc があります。次のリストを見てください。
リスト : 単語のカウント package main import ( "os" "io" "bufio" "fmt" "unicode" ) func wc(file *bufio.Reader) (int, int, int) { b, l, w := 0, 0, 0 // バイト数, 行数, 単語数 inword := false for { c, s, err := file.ReadRune() if err == io.EOF { return b, l, w } if c == unicode.ReplacementChar { fmt.Fprintf(os.Stderr, "%d バイト目で不正なコードを検出しました\n", b + 1) os.Exit(1) } else if unicode.IsSpace(c) { inword = false if c == '\n' { l++ } } else if !inword { inword = true w++ } b += s } } func main() { b, l, w := wc(bufio.NewReader(os.Stdin)) fmt.Printf("%d %d %d\n", l, w, b) }
ファイルは標準入力 (os.Stdin) から読み込みます。実際の処理は関数 wc で行います。変数 b, l, c はそれぞれバイト数、行数、単語数をカウントします。文字の読み込みは bufio のメソッド ReadRune を使います。
func (b *Reader) ReadRune() (r rune, size int, err error)
返り値は文字、バイト数、エラーです。文字が正しくない場合、U+FFFD と読み込んだバイト数 1 を返します。U+FFFD は標準ライブラリ unicode に定数 ReplacementChar として定義されています。空白文字は関数 unicode.IsSpace で判定することができます。これは ASCII コードだけではなく Unicode の空白文字もチェックしてくれます。unicode には文字種別を判定する関数が用意されています。
文字 c が空白文字であれば変数 inword を false にセットします。そして、inword が false で c が空白文字以外であれば、inword を true にセットして変数 w を +1 します。これで単語をカウントすることができます。行数は c が空白文字かつ改行文字 '\n' であれば変数 l の値を +1 します。バイト数は ReadRune の返り値 s を変数 b に加算するだけです。
それでは実行してみましょう。
$ go run wc.go hello, world こんにちは 世界 さようなら <- CTRL-D を入力 2 5 56 $ go run wc.go < wc.go 38 109 643 $ wc < wc.go 38 109 643
正常に動作しているようですね。
ご参考までに、bufio.Scanner を使ったプログラムを示します。
リスト : 単語のカウント (2) package main import ( "os" "bufio" "fmt" "unicode" "unicode/utf8" ) func wc(s *bufio.Scanner) (int, int, int) { b, l, w := 0, 0, 0 // byte 数, line 数, word 数 inword := false s.Split(bufio.ScanRunes) for s.Scan() { c, s := utf8.DecodeRune(s.Bytes()) if c == utf8.RuneError { fmt.Fprintf(os.Stderr, "%d バイト目で不正なコードを検出しました\n", b + 1) os.Exit(1) } else if unicode.IsSpace(c) { inword = false if c == '\n' { l++ } } else if !inword { inword = true w++ } b += s } return b, l, w } func main() { b, l, w := wc(bufio.NewScanner(os.Stdin)) fmt.Printf("%d %d %d\n", l, w, b) }
関数 Split で分割関数を ScanRunes に設定します。分割関数は Scan を実行する前に設定してください。トークンは文字列またはバイト列でしか取り出すことができません。バイト列を文字に変換するには標準ライブラリ unicode/utf8 の関数 DecodeRune を使います。
func DecodeRune(p []byte) (r rune, size int)
DecodeRune は引数のバイト列 p の先頭から 1 文字取り出します。返り値 r が取り出した文字、size がバイト数です。文字が正しくない場合、文字 utf8.RuneError (U+FFFD) を返します。あとのプログラムは同じなので説明は割愛させていただます。興味のある方は実際に動かしてみてください。
近年の情報機器 (パソコンや携帯など) は GUI (Graphical User Interface) が標準ですが、パソコンやサーバ向けの OS には CUI (Character User Interface または Comand Line Interface, CLI) 環境も標準で装備されています。GUI アプリケーションの作成は、ツールキットやフレームワークなどを使うことで、以前に比べれば容易になっているのですが、それでも初心者には敷居が高いと思います。
その点、小さな CLI ツールであれば初心者でも比較的簡単に作成することができます。Go 言語は CLI ツールの作成に適しているので、気軽にチャレンジしてみてください。今回は CLI ツールを作成するときに便利なコマンドラインフラグの解析について簡単に説明します。
GUI でアプリケーションを実行する場合、たとえば Windows ではアイコンをダブルクリックすればいいですね。ところが CUI の場合、ユーザーはコンピュータに対する命令 (コマンド) をキーボードから入力します。この命令を処理するプログラムを「シェル (shell)」といいます。
シェルはユーザーの命令を OS に伝える橋渡しの役割を持っています。シェルには殻、亀の甲羅、外観、というような意味がありますが、OS 全体を包み込んでいてユーザーから直接見えるプログラムであることから、名づけられたのでしょう。Unix 系 OS の場合、いろいろなシェルがありますが、Linux では標準のシェルとして bash が搭載されています。Windows ではコマンドプロンプトや Power Shell を使用するのが一般的です。
コマンドは次のような形式で入力します。
コマンド名 引数1 引数2 ...
最初はコマンド名で、その後ろに引数が必要になる場合もあります。コマンド名や引数の間は空白で区切ります。そして最後にリターンキーを入力すると、シェルがこのコマンドを実行します。
大抵のコマンドにはコマンドラインフラグ (またはコマンドラインオプション) が用意されていて、それを引数に渡すことができます。単にフラグとかオプションと呼ばれることもあります。フラグを指定することでコマンドの動作を指定したり変更することができます。
Windows では '/' の後ろのアルファベット 1 文字で表しますが、Unix 系 OS では '-' の後ろのアルファベット 1 文字で表すか、'--' の後ろの文字列で表すことが多いです。このほかにも引数でファイル名やディレクトリ名、あるいは特別なオプションを指定する場合もあります。これはコマンドによって異なります。
CLI ツールの作成で、ちょっと面倒な処理がコマンドラインフラグの解析です。Go 言語の場合、標準ライブラリ flag を使ってフラグの解析を簡単に行うことができます。flag で解析できるフラグの構文を以下に示します。
flag は ('-' or '--') + 文字列 をフラグとして認識します。そして、そのあとにフラグの値 value を指定することができます。そして、フラグとして認識できない引数または終了記号 '--' に出会うと、フラグの解析処理を終了します。
value は文字列だけではなく、真偽値、数値 (整数と浮動小数点数)、日付などいろいろな値を指定することができます。なお、1 の形式で指定できるのは真偽値だけです。また、真偽値は 3 の形式で指定することはできません。1 または 2 の形式を使ってください。
簡単な例として、真偽値、整数、文字列を受け取るフラグを定義してみましょう。これらのフラグは次の関数を使って定義します。
func Bool(name string, value bool, usage string) *bool func Int(name string, value int, usage string) *int func String(name string, value string, usage string) *string
第 1 引数 name がフラグ名、第 2 引数 value がフラグのデフォルト値、第 3 引数 usage がフラグの説明文で、自動生成されるヘルプで使用されます。ヘルプはフラグ -h で表示することができます。返り値はフラグの値を格納した変数へのポインタです。
フラグの設定が終わったら関数 Parse でフラグを解析します。
func Parse()
解析されたフラグの値は変数に格納されます。フラグが指定されていない場合、変数はデフォルト値で初期化されます。フラグを取り除いたコマンドライン引数は関数 Args で求めることができます。
func Args() []string func Arg(i int) string func NArg() int func NFlag() int
このほかに、i 番目のコマンドライン引数を返す関数 Arg、コマンドライン引数の個数を返す関数 NArg、設定されているフラグの個数を返す関数 NFlag もあります。
簡単なプログラムと実行例を示します。
リスト : flag の簡単な使用例 (test01.go) package main import ( "fmt" "flag" ) func main() { a := flag.Bool("a", false, "bool flag") b := flag.Int("b", 0, "int flag") c := flag.String("c", "oops!", "string flag") flag.Parse() fmt.Println(*a, *b, *c) fmt.Println(flag.Args()) }
$ go build test01.go $ ls test01 test01.go $ ./test01 false 0 oops! [] $ ./test01 foo bar baz false 0 oops! [foo bar baz] $ ./test01 -a -b 10 -c foo true 10 foo [] $ ./test01 -a -b 10 -c foo bar baz true 10 foo [bar baz] $ ./test01 -a=true -b=-123 -c=bar foo bar baz true -123 bar [foo bar baz]
bool flag は次の値を受け付けます。
1, 0, t, f, T, F, true, false, TRUE, FALSE, True, False
int flag は接頭辞に 0 (8 進数), 0x (16 進数), - 符号を受け付けます。
フラグ -h を指定するとヘルプが表示されます。フラグの解析に失敗した場合もエラーメッセージとともにヘルプが表示されます。
$ ./test01 -h Usage of ./test01: -a bool flag -b int int flag -c string string flag (default "oops!") $ ./test01 -b 1.2345 invalid value "1.2345" for flag -b: parse error Usage of ./test01: -a bool flag -b int int flag -c string string flag (default "oops!") $ ./test01 -abc flag provided but not defined: -abc Usage of ./test01: -a bool flag -b int int flag -c string string flag (default "oops!")
最後の例のように -abc は一つのフラグとして認識され、それは定義されていないのでエラーとなります。たとえば、bool flag として -a -b -c が定義されていても、-abc とまとめて指定することはできないので注意してください。
ヘルプは次の関数で表示することもできます。
func PrintDefaults()
メッセージは標準エラーに出力されます。
フラグの値を格納する変数をユーザーが用意して、フラグを定義する関数に渡す方法もあります。
func BoolVar(p *bool, name string, value bool, usage string) func IntVar(p *int, name string, value int, usage string) func StringVar(p *string, name string, value string, usage string)
今までの関数の後ろに Var を付けたものが関数名になります。第 1 引数が変数へのポインタで、返り値はありません。最初の例 (test01.go) を書き直すと次のようになります。
リスト : flag の簡単な使用例 (2) package main import ( "fmt" "flag" ) func main() { var a bool var b int var c string flag.BoolVar(&a, "a", false, "bool flag") flag.IntVar(&b, "b", 0, "int flag") flag.StringVar(&c, "c", "oops!", "string flag") flag.Parse() fmt.Println(a, b, c) fmt.Println(flag.Args()) }
実行例は同じなので省略します。
それでは簡単な例題として、ファイルを連結するコマンド cat を Go 言語で作成してみましょう。プログラムを簡単にするため、サポートするフラグは -b と -n だけにします。-n は行の番号を表示し、-b は空行以外の行に番号を振ります。-n と -b を同時に指定した場合は -b を優先することにします。
cat は拙作のページ ファイル入出力 で作成した cat.go を改造すると簡単です。プログラムは次のようになります。
リスト : ファイルの連結 (cat.go) package main import ( "fmt" "os" "bufio" "flag" ) // フラグ var bflag bool var nflag bool // ファイルの表示 func cat(file *os.File, n int) int { s := bufio.NewScanner(file) for s.Scan() { l := s.Text() if (bflag && l != "") || nflag { fmt.Printf("%6d %s\n", n, l) n++ } else { fmt.Println(l) } } return n } func main() { // フラグの設定 flag.BoolVar(&bflag, "b", false, "number nonempty output lines, overrides -n") flag.BoolVar(&nflag, "n", false, "number all output lines") flag.Parse() if bflag && nflag { nflag = false } // 行数 n := 1 if flag.NArg() == 0 { // 標準入力 cat(os.Stdin, n) } else { for _, name := range flag.Args() { file, err := os.Open(name) if err != nil { fmt.Fprintln(os.Stderr, err) os.Exit(1) } n = cat(file, n) file.Close() } } }
フラグの値は大域変数 bflag, nflag に格納します。関数 BoolVar でフラグを設定し、関数 Parse で解析します。bflag と nflag が共に真ならば、-n と -b が同時に指定されたので、nflag を false に書き換えます。行数は変数 n でカウントします。次に、関数 NArg の返り値をチェックして、それが 0 ならば引数 (ファイル名) の指定はないので、標準入力 (os.Stdin) からデータを読み込みます。
ファイル名の指定がある場合、関数 os.Open でファイルをオープンします。エラーが発生した場合はメッセージを表示して os.Exit で終了します。あとは関数 cat を呼び出してファイルを表示し、変数 n を cat の返り値で書き換えます。最後にオープンしたファイルを関数 Close でクローズします。
関数 cat は簡単です。引数 file がファイル、n が行数です。ファイルの読み込みは bufio.Scanner を使います。s.Scan() で 1 行ずつデータを読み込み、s.Text() で行を取り出して変数 l にセットしてます。bflag が真で行 l が "" ではない、または nflag が真ならば行番号 n を表示します。そうでなければ行 l をそのまま表示するだけです。
これでプログラムは完成です。それでは実際に試してみましょう。
$ go build cat.go $ ./cat -h Usage of ./cat: -b number nonempty output lines, overrides -n -n number all output lines $ ./cat < test.txt abcdefghijklmn ABCDEFGHIJKLMN abcdefghijklmn ABCDEFGHIJKLMN abcdefghijklmn ABCDEFGHIJKLMN $ ./cat -n test.txt test.txt 1 abcdefghijklmn 2 ABCDEFGHIJKLMN 3 4 abcdefghijklmn 5 ABCDEFGHIJKLMN 6 abcdefghijklmn 7 8 ABCDEFGHIJKLMN 9 abcdefghijklmn 10 ABCDEFGHIJKLMN 11 12 abcdefghijklmn 13 ABCDEFGHIJKLMN 14 abcdefghijklmn 15 16 ABCDEFGHIJKLMN $ ./cat -b test.txt test.txt 1 abcdefghijklmn 2 ABCDEFGHIJKLMN 3 abcdefghijklmn 4 ABCDEFGHIJKLMN 5 abcdefghijklmn 6 ABCDEFGHIJKLMN 7 abcdefghijklmn 8 ABCDEFGHIJKLMN 9 abcdefghijklmn 10 ABCDEFGHIJKLMN 11 abcdefghijklmn 12 ABCDEFGHIJKLMN
正常に動作しているようです。興味のある方は他のフラグを追加するなど、プログラムをいろいろ改造してみてください。