C についての基礎的な知識があることを想定。C と違う点を重点的に解説する。
https://go.dev/dl/
例を PC Linux にとれば go1.17.6.linux-amd64.tar.gz
をダウンロードすればよい。これをどこかに置く1。僕の場合には(今後、何回もバージョンアップがあることを想定して)置き場所を
GOLANG=$HOME/src/golang
$GOLANG/go
が生成され、ここにパッケージが展開されている。Go のソース・ファイルをコンパイルするのに必要な実行ファイルが $GOLANG/go/bin
に置かれている。
環境変数
GOROOT=$GOLANG/go; export GOROOT PATH=$PATH:$GOROOT/bin/go; export PATH
$GOLANG
にインストールされていると仮定する。従って $GOLANG/go
が既に存在する。その下でgo1.17.6.src.tar.gz
cd $GOLANG mv go bootstrap tar -xf go1.17.6.src.tar # produces go GOROOT_BOOTSTRAP=$GOLANG/bootstrap cd go/src all.bashすると次のメッセージを得る:
all.bash Building Go cmd/dist using /home/arisawa/src/golang/bootstrap. (go1.17.6 linux/amd64) Building Go toolchain1 using /home/arisawa/src/golang/bootstrap. Building Go bootstrap cmd/go (go_bootstrap) using Go toolchain1. Building Go toolchain2 using go_bootstrap and Go toolchain1. Building Go toolchain3 using go_bootstrap and Go toolchain2. Building packages and commands for linux/amd64. ##### Testing packages. ok archive/tar 0.060s ok archive/zip 0.142s ... ... ALL TESTS PASSED --- Installed Go for linux/amd64 in /home/arisawa/src/golang/go Installed commands in /home/arisawa/src/golang/go/binこれで終了。
http://p9.nyx.link/golang/
で解説されている。この解説が参考になるかも知れない。
完全な文法は
The Go Programming Language Specification
https://go.dev/ref/spec
に書かれている。しかし、これを初心者が読むのはしんどいだろう。
the result
basg$ go run a.go こんにちは 23 60 6 2 0 1 2 3 4 5 6 7 8 9 -bash$
;
" に置き換えられて解釈される。";
" は C と同様に命令の区切り子である。func main() {と書くとシンタックスエラーになる。なぜなら、
func main();{と解釈されるから。
{ }
"{ }
" を省けない。
任意
環境変数:
GOROOT
: 配布パッケージの場所を知らせる。配布パッケージはどこに置いてもよい。
GOPATH
: ユーザー作成(サードパーティを含む) の Go 関係のファイルの場所を知らせる。
Go のプログラム自体はどこに置いても可。しかし $GOPATH/src
の下に置くのが管理上望ましい。
go env GOPATH
GOPATH
が判る。GOPATH
が環境変数として定義されていない場合には$HOME/go
GOPATH
であるとされる。
package
宣言は必須main
を含む場合には main
を指定
io.Args
を見よ。return 0
あるいは exit(0)
で終わる必要はない。
var x,y int
変数の型の決定は、明示的な宣言によるか、または代入による。
以下の3つのスタイルがある:
(1) var x,y,z int // x=0, y=0, z=0 で初期化
(2) var x,y,z = 2,3,5 // x=2, y=3, z=5 で初期化
(3) x,y,z := 2,3,5 // x=2, y=3, z=5 で初期化
注1: (3) は代入文と言うより、宣言文である。
(2) と (3) の実質的な違いはどこにあるか? → 注3 および 注4 を見よ
そもそも (2) は言語設計として必要なのか? → 注4 を見よ
こんな書き方も許される:
var( a = 2 b = "foo" c = true )関数の中でも、外でも OK
注2: (1) のタイプのように、明示的な型宣言はプログラミングの大きな負担である。僕が Java を使わない最大の理由がこの点にある。インタープリタ言語の多くは、型宣言を要しないので気楽にプログラムができる。他方では可読性を害し、バグの原因になり、バグ取りを困難にする。Go はこの問題を解決しようとしている。
注3: (1) と (2) のタイプは関数の外で使えるが、(3) のタイプは関数の外では使えない。
注4: (3) のタイプは、その変数が利用されていないとコンパイラに文句を言われ、コンパイルできない。他方、(1) や (2) のタイプは、関数の外で宣言された場合には、その変数が利用されていなくてもコンパイラは文句を言わない。(この特徴は package の中で宣言あるいは初期化された多数の変数を利用できるようにするために必要である。)
"abc"+"de"
var a byte; var b int
a + b
int(a)+b
println
は改行を伴う。以下に標準出力に吐き出す類似の関数を挙げる。print println fmt.Print fmt.Println fmt.Printf
print()
と println()
は原始的な出力関数で、正式のプログラムコードでは使わない方が良い。デバッグ用に限定して使える。原始的なるが故に、Go の動作を理解するのに良いかも知れない。
print()
は改行を伴わない。また、データとデータの区切りに空白を付加しない。
print()
や println()
は、ポインタ変数に対してはアドレスを出力する。他方参照しているデータを書き出すには fmt
package の関数を使う。
fmt
を使う時には import
宣言が必要
package main import ( "fmt" )のように宣言しておいて
fmt.Println("Hello")
fmt.
" が面倒ならpackage main import ( . "fmt" )とする。すると
Println("Hello")
fmt.
" を省ける。なぜ原始的な出力関数を大文字で始めなかったかが理解できるだろう。なお Go の名前規則の中に、小文字で始める名前は package の中で private、大文字で始める名前は public というのがある。public な名前は、他の package から名前が参照される。
ここに挙げた出力関数はバッファリングしないので遅い。デバッグ用。
バッファリングが必要なら、"bufio
" を使う。
"a string in double quote" `a string in back quote`
'a' 'あ'
' '
で囲む。この辺は C と同じであるが、許される文字は Unicode に拡張されている。
' '
で括ったつもりでも、1文字と認識しないのでエラーになるだろう。
rune
は uint32
の別名
byte
は uint8
の別名
C 風の書き方がサポートされている。
for k:=0; k < n; k++ { print(k," ") }
しかし次の書き方は NG で要注意。
package main import( . "fmt" ) func f(n,m int) bool{ if n >= m { return false } return true } func main(){ ok := true for k:=0;ok;k++ { //ok = f(k,3) // OK ok := f(k,3) // NG Println(k,ok) } }
うまく行かない原因は、for
の条件部の ok
は for
の本体ブロックの中での ok
とは異なるからである。ここは ":=
" ではなく "=
" としなくてはならない。(コンパイラがチェックしてくれるべきだと思う)
for(k=0; k < n; k++) { ...... }
( )
が無い。無くても意味が通じるから。{ }
が必要である。( { }
の中に命令が1つしか無い場合も):=
が使われている。k
は for
の中だけの一時変数だから。
注2:
while ~ {....}
や do {....} until ~
は Go には無い。for
だけでやっていく。
詳しくは後に。
package main // Ssanf // go doc fmt.Sscanf import ( . "fmt" ) func main() { var x,y int // func Sscanf(str string, format string, a ...interface{}) (n int, err error) input := "123 45" Println(input) n,err := Sscanf(input, "%d %x",&x, &y) Println(err,n,x,y) s := Sprintf("%d %x", x, y) Println(s) }
output:
123 45 <nil> 2 123 69 123 45
文字に関する、いくつかの基本型: string
, byte
, rune
package main // quote import "fmt" func main(){ var s = "こんにちは" var b = []byte(s) var t = string(b) var u = 'あ' // this type is rune println(s) // こんにちは println(b) // [15/15]0xf840028220 fmt.Println(b) // [227 129 147 227 130 147 227 129 171 227 129 161 227 129 175] println(t) // こんにちは fmt.Printf("%x\n",u) // 3042 } /* output is shown at the right side of "//" 3042 is 0x3042, unicode of 「あ」 */
// これより右がコメント (行末まで) /* この中がコメント */
" "
で囲っているのはそのためであろう。var s = "こんにちは" var b = []byte(s)
[]byte
var b = []byte(s)
s
を基に slice 型データが作られる。「基に」と言ったのは、b
が指し示しているのは s そのものではないからである。(s
のコピーである)
この例題で示されているように、string と byte 配列 ( []byte ) は相互に変換できる。
string はもっぱら UTF-8 でエンコードされた文字列のための変数型であるが、任意の byte 列を string に変換できるようである。次の例で、string と []byte ととの違いを示す。
var a string var b []byte a = "Hello" b = []byte(a) fmt.Println(a, a[2:5],b[2:5]) // Hello ell [108 108 111]
s
の長さは関数 len(s)
で分かる。len("こんにちは")
は 5 ではなく、15 を返す。15 は "こんにちは"
を UTF-8 でエンコードしたときの byte 数である。
補注: string と言いながら string らしからぬこの規則は変なのである。
package main import "fmt" func main(){ var s = "こんにちは" var b = []byte(s) var t = string(b) println(len(s),s) // こんにちは println(len(b),b) // [15/15]0xf840028220 fmt.Println(b) // [227 129 147 227 130 147 227 129 171 227 129 161 227 129 175] println(len(t),t) // こんにちは b[0] = 0 b[1] = 0 b[2] = 0 t = string(b) println(len(t),t) println(len(s),s) }この実行結果は
15 こんにちは 15 [15/15]0xf840028220 [227 129 147 227 130 147 227 129 171 227 129 161 227 129 175] 15 こんにちは 15 んにちは 15 こんにちは
数字を表す文字列から数字を生成するに、
fmt.Sscanf()
strconv.Atoi
を使う。package main import( . "fmt" "strconv" ) func main(){ var s = "123" var x int x,err := strconv.Atoi(s) Println(err,x) n,err := Sscanf(s, "%d", &x) Println(n,err,x) }output
<nil> 123 1 <nil> 123
":="
で定義される。(宣言と初期化を兼ねている)":="
の効果をみるプログラムを示す。package main func main(){ x:=3 // temporal variable, use ":=" without "var". println(x) // 3 { x=x+2 println(x) // 5 x:="abc" println(x) // abc } println(x) // 5 }
コメント "//
" の右に println()
の出力が示されている。
同じ変数名 x
を使っていても、実体は異なる変数であることが分かる。
package main func main(){ var x int for x:=0; x <3; x++ { println(x) } println(x) // o for x=0; x < 3; x++ { println(x) } println(x) // 3 }
for 文のための制御変数のために、わざわざ var
で変数宣言するのは嫌だから、":=
" を導入したらしい。
しかし、伝統的な C のスタイルだと、繰り返しが最後まで行われたことが、容易に分かるメリットもある。
package main import "fmt" func main(){ var primes = [10]bool{} primes[2]=true primes[3]=true primes[5]=true primes[7]=true for i:=2; i<10;i++{ fmt.Printf("%d:%-8v",i,primes[i]) } fmt.Println() }
譜 array2.go
あいるは
package main import "fmt" func main(){ var primes = [10]bool{ 2: true, 3: true, 5: true, 7: true, } for i:=2; i<10;i++{ fmt.Printf("%d:%-8v",i,primes[i]) } fmt.Println() }
[10]bool{}
" で初期値を指定するが、この場合には指定されていないので、初期値は false
となる。
Printf
の書式の中の "%v
" は、変数の型に合わせて適当にやってくれる。"%s
" や "%d
" の代用になる。
なお Printf
の書式の中の "%T
" は、変数の型を表す。
-bash$ go run array2.go 2:true 3:true 4:false 5:true 6:false 7:true 8:false 9:false -bash$
次に、配列のサイズを、初期値から決める例を挙げる。
package main import "fmt" func main(){ var days = [...]string{"sun","mon","tue","wed","thu", "fri","sat"} for i:=0; i<len(days);i++{ fmt.Printf("%5s",days[i]) } fmt.Println() }
...
」は任意個
を表す。
for _,v := range days { fmt.Printf("%5s",v) }
-bash$ go run array1.go sun mon tue wed thu fri sat -bash$
var days = [...]string{"sun","mon","tue","wed","thu", "fri","sat"}
var days = []string{"sun","mon","tue","wed","thu", "fri","sat"}
package main import "fmt" func main(){ var a = [...]string{"sun","mon","tue","wed","thu", "fri","sat"} var b = a a[1] = "lua" for i:=0; i<len(a);i++{ fmt.Printf("%5s",a[i]) } fmt.Println() for i:=0; i<len(b);i++{ fmt.Printf("%5s",b[i]) } fmt.Println() }
譜 array3.go
このプログラムでは b
もまた array であり、a
とは独立したデータを保有している。(a
のデータがコピーされている。) 従って、a
を変更しても b
は変更されない。この事は次の結果で分かる。
-bash$ go run array3.go sun lua tue wed thu fri sat sun mon tue wed thu fri sat -bash$
slice 型変数はデータの本体を保有しない。(実際には array へのポインタである。)
次のプログラムでは a
も b
も共通のデータへのポインタなので、a
を変更する(a
を通じて本体を変更する)と、
package main import "fmt" func main(){ var a = []string{"sun","mon","tue","wed","thu", "fri","sat"} var b = a a[1] = "lua" for i:=0; i<len(a);i++{ fmt.Printf("%5s",a[i]) } fmt.Println() for i:=0; i<len(b);i++{ fmt.Printf("%5s",b[i]) } fmt.Println() }
譜 array4.go
結果は次のように、b
も変更される。
-bash$ go run array4.go sun lua tue wed thu fri sat sun lua tue wed thu fri sat -bash$
package main import "fmt" func main(){ a := [4]int{} a[0] = 2 a[1] = 3 a[2] = 5 a[3] = 7 fmt.Println(a[1:3]) // [3 5] }
この例では a[1:3]
の値は [3 5]
である。これは、a[1]
と a[2]
の値である。a
を配列とすると
a[n:m]
a
の部分配列(a[n],a[n+1],...,a[m-1]
)が得られる。n,m
は次の関係を満たさなくてはならない:0 <= n <= m < len(a)
package main import ( . "fmt" ) func main() { var a = [3]int{2,3,5} var b = [2]int{7,11} // Println(append(a[:],b[:])) //NG Println(append(a[:],b[:]...)) //OK [2 3 5 7 11] Println(append(a[:],b[0],b[1])) //OK [2 3 5 7 11] }
b[:]
は b[0:]
と同じである。全体を slice として扱いたい場合には、この方が良いであろう。b[:]...
は奇っ怪なように感じられるが、これは配列の要素を丸ごと関数の引数として渡したい場合に便利である。またこのテクニックを知らないと困ることがある。例えばデバッグ用の dbgPrintln()
を作りたいときなど..
make([]int,5,100)
配列の構成に必要な要素の個数が、実行してみないと分からない場合がある。(そのようなケースはドキュメント処理では極く普通である。)
Python では、そのようなケースは List で切り抜けている。Go はあくまで伝統的な配列を使う。(速度が速い)
配列の大きさが必要に応じて変化するような配列を定義するには make() を使う。次のプログラムで make() の使い方と特性を示す。
package main func main(){ a := make([]int,5,100) a[0] = 2 a[1] = 3 a[3] = 5 println("len:",len(a)) a = append(a,7,11,13) println("len:",len(a)) for i:=0; i<len(a);i++{ println(a[i]) } }このプログラムの
a := make([]int,5,100)
a
のサイズを 5 として、100 まで大きくなる初期値を与えている。実際には、100 を超える事が許されるが、100 を超えるとメモリーの再配置が内部で行われる(そのために時間のロスが発生する)可能性があり、適切な初期値を与えた方が良い。a := make([]int,5)
指定された配列の初期値(ここでは5)を超える部分への追加は 関数 append()
で行う。
プログラムの実行結果は次のようになる。
len: 5 len: 8 2 3 0 5 0 7 11 13
初期配列を超える要素(ここでは a[n]
(n >4) )の参照は、append()
でデータが与えられていない限り実行時のエラーになる。この仕様は安全性のために当然である。
配列の要素には暗黙の初期値(整数の配列の場合には 0)が存在する。つまり明示的に初期化されていない要素が式の中に使われてもエラーにならない。この仕様は危険なのであり、安全性のためには Lua のように、nil
を返した方が良い。しかし、Go における配列の概念はCの配列と同じで、要素は型で示されているとおりである。そのために nil
は返せない。そもそも整数型の変数には、初期化されたのか否かの印を付ける事ができないのである。
make([]int,0)
make([]int,5)
の特殊ケースである。
package main import ( . "fmt" ) func main() { var a = make([]int,0) for k:=0; k <10; k++ { a = append(a[:],2*k) } Println(a) }
output:
[0 2 4 6 8 10 12 14 16 18]
リストは package によってサポートされている。package は
import "container/list"
http://golang.org/pkg/container/list/
map のキーを整数に限定すると、配列のように使える。
package main func main(){ var d = map[int]string{} d[1]="alice" d[2]="bob" d[6]="frank" println(len(d)) for k,v := range d { println(k,v) } }
結果:
3 1 alice 2 bob 6 frank
package main func main(){ var d = map[int]string{1:"alice",2:"bob",6:"frank"} println(len(d)) for k,v := range d { println(k,v) } }
v,ok := d["david"]
package main func main(){ var d = map[string]int{"alice":12,"bob":18,"carol":22} v,ok := d["alice"] println(v,ok) // 12, true v,ok = d["david"] println(v,ok) // 0, false }
なお、map に登録されていないキーの要素の値は Zero value (整数の場合には 0
, 文字列の場合は ""
, ポインタの場合には nil
) となる。Zero value だけを見て、登録の有無を判断できないのは、この例をみても明らかである。
存在しないキーを使ってもエラーにならない!
package main func main(){ var d = map[string]int{"alice":12,"bob":18,"carol":22} n := d["alice"] + d["david"] println(n) // 12, not error //n,ok := d["alice"] + d["david"] // error, assignment count //println(n,ok) }
ここでは構造体 "struct
" の使い方(宣言の方法、値の与え方など)を例題で示す:
// lesson to structured variables package main import ( . "fmt" ) var p [2] float64 type Point struct { x,y float64 } var q Point type Person struct { name string age int } var a1 = Person {"alice", 16} type T struct { name string age int etc U } type U struct { mail string tel string } func main() { Println(p) p[0] = 2.1 p[1] = 1.2 //p[2] = 3.1 // this should be error Println(p) // which is same as: p = [2] float64 {2.1, 1.2} Println(p) Println(q) q.x = 2.1 q.y = 1.2 Println(q) // which is same as: q = Point{2.1, 1.2} Println(q) Println(a1) b := T{"bob",18,U{"bob@gmai.com","0123-456-789"}} Println(b) }
output:
[0 0] [2.1 1.2] [2.1 1.2] {0 0} {2.1 1.2} {2.1 1.2} {alice 16} {bob 18 {bob@gmai.com 0123-456-789}}最後の行を見て分かる通り、
Println(b)
は構造体の深部をちゃんと書き出している。
package main import ( . "fmt" ) type Foo struct{ int x,y int } func main() { var a Foo a.int = 5; a.x = 2; a.y = 3 Println(a) }
ここに現れた
type Foo struct{ int x,y int }は
int int
x,y int
a.int = 5
次の例は、コマンド引数の処理を示す。
package main import "os" func main(){ for n:=0; n < len(os.Args); n++ { println(n,os.Args[n]) } }
package main import "os" func main(){ for n,v := range os.Args { println(n,v) } }
xxx.go
とすると、コンパイルと実行はgo build xxx.go && ./xxx alice bob caro
xxx
が生成される。
なお、実行ファイル xxx.go
の生成をしないならば、
go run xxx.go alice bob carol
xxx.go
の所で Go のソース・ファイルを複数個並べると、違った意味になるので注意。
実行ファイル xxx
を $GOPATH/bin
の中に生成したければ
go install xxx.go
#include <stdio.h> int a = f(3); // compile error int f(int x) { return 2*x; } int main(int argc,char **argv){ printf("OK %d\n",a); return 0; }
次のように、関数の中で初期化しなくてはならない。
#include <stdio.h> int a; int f(int x) { return 2*x; } int main(int argc,char **argv){ a = f(3); printf("OK %d\n",a); return 0; }
# a=f(3) # error def f(x): return 2*x a=f(3) print "OK",a
Python に限らず、大域変数が実行時に(関数の外で)初期化できることは、スクリプト言語の常識である。
ただし、a=f(3)
が実行される時点で f(x)
が定義されていなくてはならない。
package main var a = f(3) func f(x int) int { return 2*x } func main(){ println("OK",a) }
この仕組みを使うと、例えば、コマンド引数を基に大域変数を次のように初期化できる。
package main import ( "os" "strconv" ) var b,err = strconv.Atoi(os.Args[1]) var a = f(b) func f(x int) int { return 2*x } func main(){ println("OK",a) }
var
の部分は次のように書いてもよい。
var ( b,err = strconv.Atoi(os.Args[1]) a = f(b) )
flag package に応用の具体例が見られる。
コマンド引数の情報を、関数 main
の引数ではなく、os.Args
でプログラムに渡す設計にしたのは、このような事情があるのかも知れない。
package main import ( "os" "log" ) func main(){ var wr = os.Stdout var bs []byte bs = make([]byte,4*1024) for _,f := range os.Args[1:] { rd,err := os.Open(f) if err != nil { log.Fatal(err) } for n,err := rd.Read(bs) ; n > 0; { if err != nil { log.Fatal(err) } wr.Write(bs) n,err = rd.Read(bs) } rd.Close() } }
import ( "os"; "log" )
;
" ではなく、",
" で区切りたくなるが、";
" にしたのは、例題のように、一つの package を一つの行に書かせたかったのだろう。もしも、",
" で区切るルールにすると、package 名のあとに ",
" を書かなくてはならなくなる。
var bs []byte bs = make([]byte,4*1024)
var bs = make([]byte,4*1024)
_
" が準備されている。変数 "_
" はゴミ箱のような変数で、ここに代入された値が使用されなくても、コンパイラは文句を言わない。
関数の外で定義された変数に関しては、それを利用していなくてもコンパイラは文句を言わない。
if err != nil {
nil
でない値は true
と見なされない)
log.Fatal()
はエラーの理由をログファイルに書き出し、os.Exit(1)
(異常終了) で終わる関数である。ログファイルが指定されていない場合には、標準エラーに出力する。
この例では for
文のブロックの中に ":=
" が使われている。具体的には
rd,err := os.Open(f)
for
文のブロックの中における ":=
" の使用はできるだけ避けるべきである。C とは異なり golang では ":=
" で定義された変数は、for
文の条件部に現れる変数とは(たとえ同名の変数であっても)関係ない。そのために繰り返しの停止条件に関わる変数を for
プロックの中で ":=
" で与えると無限ループに陥ることがある。
こり例題では
defer bufio.NewReader() bufio.ReadString() os.WriteString() err.Error() for
package main import ( "os" "bufio" "log" ) func parse(path string){ var line string file,err := os.Open(path) if err != nil { log.Fatal(err) } defer file.Close() rd := bufio.NewReader(file) wr := os.Stdout line,_ = rd.ReadString('\n') for len(line) > 0 { // do something here, for example: wr.WriteString(line) line,_ = rd.ReadString('\n') } } func main(){ parse(os.Args[1]) }
関数 parse()
の中の
defer file.Close()
defer
に続く関数が parse()
の終了時に実行される事を指定している。defer
で指定される関数は「後始末関数」である。エラー処理の後にファイルをクローズするのを忘れがちになるのだが、defer
を使えば、そうしたバグから解放される。
defer
は複数指定してもよく、後に指定した後始末関数から順に実行される。
bufio
" の使用例を示す。ReadString
が便利である。この関数は、一行の文字数があらかじめ分かっていない(つまり非常に長い行が含まれている可能性がある)場合に使われる。
ReadString()
が返す値はファイルに含まれる 1行の文字列であり、行の区切り文字(このケースでは '\n'
) を末尾に含む。
UNIX の伝統的なテキストファイルの形式では、ファイルに含まれる最後の文字は '\n'
である。しかし、この形式に従わないテキストファイルが存在するので注意が必要である。
bufio.ReadString()
は Plan9 の readstr()
の Go での実装である。readstr()
は C の関数であるために、得られた行は明示的に free()
でメモリを解放する必要があった。しかし、bufio.ReadString()
の場合は、メモリの解放はガーベッジコレクタが行っているはずである。
ReadLine()
があるが、こちらは(メモリ効率は良いが)運用に次の注意が要求される。ReadLine()
で破棄される。ReadLine()
はあらかじめ一行の最大サイズが分かっていない場合に使うとバグを発生させるか、あるいは面倒な処理が要求される。また得られた行を蓄積して、ファイルを読み終わってから解析するわけには行かない。ReadLine()
相当の関数はサポートされないのであるが、Go は C の細やかさも追求しようとしているのだろう。( Go の ReadString()
は Python の readline()
に相当する )
for len(line) > 0 {
for 論理式 {
もちろん、この部分は
for ; len(line)>0; {
'\n'
で終わらないテキストファイルに対しては、'\n'
を追加する。
package main import ( "os" "bufio" "log" ) func parse(path string){ var line string file,err := os.Open(path) if err != nil { log.Fatal(err) } defer file.Close() rd := bufio.NewReader(file) wr := os.Stdout line,err = rd.ReadString('\n') for err == nil { // do something here, for example: wr.WriteString(line) line,err = rd.ReadString('\n') } if len(line) > 0 { wr.WriteString(line+"\n") } } func main(){ parse(os.Args[1]) }
読み取った行のサイズが正であるにも係わらず、err
が発生するのは '\n'
で終わっていないからである。このケースでは err == io.EOF
となっている。
普通のファイル、つまりファイルに含まれる文字数が判っているファイルでは、その情報を利用してファイルを読み取ることができる。この場合に簡便なのは os.ReadFile()
である。次に例を示す。
package main import( . "fmt" "os" ) var usage = "usage: read1 file" func Error(s interface{}){ Fprintln(os.Stderr,s) os.Exit(1) } // look src/os/file.go for os.ReadFile() func main(){ args := os.Args[1:] if len(args) != 1 { Error(usage) } b,err := os.ReadFile(args[0]) if err != nil{ Error(err) } Print(string(b)) }
ファイルの内容を全てメモリーに蓄えるので、大きなファイルには向かない。
読み取った結果をメモリーに蓄えたいのだが、予め必要なメモリーのサイズが判らないことがある。例えば標準入力からの読み取りの場合である。そのような場合には os.ReadFile
は役に立たない。しかし io.ReadAll(rd)
が使える(次を見よ)。 この例は Hget でも使われている。
なお関数 Error()
における interface{
} については「型無し変数」を見よ。
package main import( . "fmt" "os" "io" "crypto/sha256" ) var usage = "sha256sum [file]" func Error(s interface{}){ Fprintln(os.Stderr,s) os.Exit(1) } func main(){ var err error var bs []byte args := os.Args[1:] rd := os.Stdin switch(len(args)){ case 0: case 1: f := args[0] rd,err = os.Open(f) if err != nil { Error(err) } default: Error(usage) } bs,err = io.ReadAll(rd) if err != nil { Error(err) } rd.Close() sum := sha256.Sum256(bs) Printf("%x\n",sum) }
複数のファイルが指定された場合にも対応すべきだが、サボられている。
package main import "fmt" type typeless interface{} func main(){ var x typeless x = 3 println(x) println(x.(int)) // 3 fmt.Println(x) // 3 x = "alice" println(x) println(x.(string)) // alice fmt.Println(x) // alice }このプログラムでは、変数
x
は、整数 3
の代入を許したり、文字列 "alice"
の代入を許したりしている。このような柔軟な変数はインターフェース型変数の特殊ケースであり、型名をvar x interface{}
注: このプログラムでは、interface{}
を typeless
として定義している。これは僕の好みである。
var x typeless x = 2 println(x + 3) // error
var x typeless x = 2 println(x.(int) + 3) // OK
Golang では Python (あるいは現代的な言語)のように複数の戻り値を指定できる。これは非常にありがたいのである。2つの方法を関数 f(x,y)
と関数 g(x,y)
で示す。関数 f(x,y)
は普通のスタイルである。他方 g(x,y)
は他の言語に慣れているとちょっと奇っ怪であるが、エラーコードを返すのに便利なように設計されている。次にそのサンプルを示す。
package main import ( . "fmt" "os" ) var usage = "usage: a1" func Error(s interface{}){ Fprintln(os.Stderr,s) os.Exit(1) } func f(x int,y int) (int, int) { return x+y, x*y } func g(x int, y int) (sum int, prod int) { sum = x+y prod = x*y return } func main() { Println(f(2,3)) // output: 5 6 Println(g(2,3)) // output: 5 6 } /* look https://go.dev/ref/spec for details */
package main func f(args ... int){ for k,v := range args{ println(k,v) } } func main(){ f(2,3,5) }結果は
0 2 1 3 2 5宣言
args ... int
args
が int
型の可変個数引数である事を示している。
この例では可変個数部分の全てが単一タイプ( int
)であるが、型の部分も含めて自由にデータを関数に渡したい場合にはどうするか?
次のプログラムがヒントになる。
package main import "fmt" func f(args ... interface{}){ for k,v := range args{ println(k,v) fmt.Println(k,v) } } func main(){ f(2,3,"alice") }この出力は
0 (0x56820,0xf800000002) 0 2 1 (0x56820,0xf800000003) 1 3 2 (0x56a60,0xf840028220) 2 alice
package main import( . "fmt" "errors" ) // $GOROOT/src/errors/errors.go func main(){ err := errors.New("blah blah") Println(err) }
ポインタ(pointer)は C と同様にメモリーのアドレスである。ポインタにおける "*
" と "&
" の使い方は C の方法を踏襲している。
他の言語に比べて C ではポインタを使えることが強力な武器になったが、他方では(コーディングミスによる)危険性を孕んでいた。この問題は、配列へのポインタとして slice
型を導入することによって回避している。
slice
変数とは(与えられた)配列の範囲を表す情報を含む変数で (ポインタ、長さ、容量) の3つから構成されている1。例えば a
を長さ7の配列とすると a[2:5]
で a[2],a[3],a[4]
から構成される a
の部分配列が得られるが、このときのポインタは a[2]
を指しており、長さは 3 、容量は 7 である。容量は許容されるインデックスの範囲を知るのに利用される。
Slice 変数は関数に部分配列を渡すのに使われることが多い。ポインタ変数も関数引数に使われることもあるが、使い方の例を次に示す。
package main import ( . "fmt" "unsafe" ) func f(a []int){ Println(a) } func g(a *[]int){ Println(*a) } func main() { var n int var a [7]int var b []int n = 1 for k:=0; k<7; k++ { n *= 2 a[k] = n } b = a[2:5] Println(unsafe.Sizeof(b)) // output: 24 Println(b) // output: [8 16 32] f(b) // output: [8 16 32] g(&b) // output: [8 16 32] // g(&a[2:5]) // NG }
この output は
24 [8 16 32] [8 16 32] [8 16 32]である。
unsafe.Sizeof(b)
はデバッグ用の特殊な関数で b
のサイズを知るのに使われる。もちろん CPU に依存する。unsafe
にはこの他 CPU 依存性を持ったいろいろなデバッグ用の関数が存在する。
f(b)
は f(a[2:5])
と置き換えても構わないが、g(&a[2:5])
は許されない。slice 変数は十分に軽量であるから、敢えてポインタで渡す必要はないのである。
look alse https://go.dev/ref/spec
copy
が準備されている。package main import ( . "fmt" ) func main(){ var a = [5]int{2,3,5,7,11} var b []int var c [5]int var n int c = a Println(a,c) // [2 3 5 7 11] [2 3 5 7 11] n = copy(b,a[1:4]) Println(n,a,b) // 0 [2 3 5 7 11] [] n = copy(c[3:],a[1:4]) Println(n,a,c) // 2 [2 3 5 7 11] [2 3 5 3 5] n = copy(a[3:],a[1:4]) Println(n,a) // 2 [2 3 5 3 5] }
Go の正規表現は Perl, Python などの unix でポピュラーなスクリプ言語のシンタックスを受け継いでいるが、これらの「正規表現」には問題があるとのことで[2]、全てがサポートされてはいない。詳しくは
go doc regexp/syntax
$GOROOT/src/regexp/regexp.go
grep
である。ただし flag や option はサポートされていない。grep
は貧弱であり egrep
の方が使いやすい。Go の正規表現は egrep
の正規表現に近い。
package main import ( . "fmt" . "regexp" "os" bio "bufio" ) var usage = "usage: grep expression [file ...]" func Error(s interface{}){ Fprintln(os.Stderr,s) os.Exit(1) } func grep(re *Regexp, fd *os.File){ var m bool rd := bio.NewReader(fd) line,err := rd.ReadString('\n') for err == nil { m = re.Match([]byte(line)) if m { Print(line) } line,err = rd.ReadString('\n') } if len(line) > 0 { // input doesn't end with '\n' Println(line) } } func main(){ args := os.Args[1:] if len(args) == 0 { Error(usage) } expr := args[0] re, err := Compile(expr) if err != nil { Error(err) } // Printf("re: %T\n",re) // *regexp.Regexp args = args[1:] if len(args) == 0 { // Printf("Stdin: %T\n",os.Stdin) // *os.File grep(re, os.Stdin) os.Exit(0) } var fd *os.File n := len(args) for k:=0; k < n; k++ { fd, err = os.Open(args[k]) grep(re, fd) fd.Close() } }
譜 grep.go
ここに現れた
rd.ReadString('\n')
re.Match([]byte(line))
rd
や re
) に対する作用を表しているシンタックスで、Python ではよく使われる。しかし Python ではいわば便法で、「対象」の部分は関数引数に移せるが、golang は違う。例えばMatch(expr, []byte(line))
expr
" は string
で、これを基に Compile
が行われ、結果が re.Match([]byte(line))
に渡される。1回きりの場合にはそれでも良いが、この例のように何回も実行される場合には、効率が悪くなる。
これらの使い方は、Go のソースコードを直接調べなくても
hebe% go doc regexp.Match package regexp // import "regexp" func Match(pattern string, b []byte) (matched bool, err error) Match reports whether the byte slice b contains any match of the regular expression pattern. More complicated queries need to use Compile and the full Regexp interface. hebe% go doc bufio.ReadString package bufio // import "bufio" func (b *Reader) ReadString(delim byte) (string, error) ReadString reads until the first occurrence of delim in the input, returning a string containing the data up to and including the delimiter. If ReadString encounters an error before finding a delimiter, it returns the data read before the error and the error itself (often io.EOF). ReadString returns err != nil if and only if the returned data does not end in delim. For simple uses, a Scanner may be more convenient. hebe%でも判るはずであるが1、なぜか
Match
については完全ではない。実は Match
は regexp.go
の中に2つある:func (re *Regexp) Match(b []byte) bool func Match(pattern string, b []byte) (matched bool, err error)
このプログラムを書いていて気がついたことを記す。
import
の中で
bio "bufio"
. "bufio"
"bufio"
はもう "fmt"
で import されている」と文句を言われたからである。そこで ".
" ではなく "bio
" で切り抜けた次第である。この部分は少し不自然なので、もっと良い方法があるかも知れない。
プログラムの中で
// Printf(....)
grep
を作る際に、変数の型を調べる必要があったからである。
なお関数 grep
を
func grep(re *Regexp, fd *File){ var m bool rd := bio.NewReader(fd) line,err := rd.ReadString('\n') for err == nil { m = re.Match([]byte(line)) if m { Print(line) } line,err = rd.ReadString('\n') } }とした場合には、改行コードを含まない「行」が grep で引っかからない。例えば
echo -n alice | ./grep ali
昔のようにテキストファイルが必ず改行コードで終わる時代は過ぎ去った。現在主に使われているテキストエディタは行エディタではなく文字エディタである。そしてコンピュータの利用者の裾野が広がった結果、改行コードで終わらないテキストファイルが氾濫している。そのようなテキストファイルに対しても正しく動くプログラムが求められる時代になっている。
hebe%
" はプロンプトであるgo env GOROOT
$GOROOT
" がここに設定されているはずである。(設定されていなければ設定すべし)regexp.go
は$GOROOT/src/regexp/
egrep '^func.* Match\(' $GOROOT/src/regexp/*.go
[1] Syntax
https://github.com/google/re2/wiki/Syntax
[2] Russ Cox: Regular Expression Matching Can Be Simple And Fast
https://swtch.com/~rsc/regexp/regexp1.html
net
package にある net.Dial
を使った簡単なプログラム例を示す。package main import ( . "fmt" "os" "net" bio "bufio" ) var args []string var usage = "usage: dial addr:port (addr is sysname or IP address)" func Error(s interface{}){ Fprintln(os.Stderr,s) os.Exit(1) } func main() { args = os.Args if len(args) != 2 { Error(usage) } conn, err := net.Dial("tcp", args[1]) if err != nil{ Error(err) } rd := bio.NewReader(conn); b, err := rd.ReadString('\n') for ; err == nil; { Print(string(b)) b, err = rd.ReadString('\n') } if len(b) > 0 { Println(string(b)) } }
譜 dial.go
このプログラムは相手サーバーに tcp で接続して、相手の応答を返す。例えば onamae.com
は接続したクライアントの IP アドレスを教えてくれる:
dial ddnsclient.onamae.com:65000
IPV4: 123.48.102.187 .のようなものである。これは我が家のホーム GW の WAN 側の IP アドレスである。WAN 側の IP アドレスは、ホーム GW に直接アクセスしても判るが、面倒である。外部のサーバーから教えてもらった方が速い。ネット上にはこのようなサービスを行っているサーバーがいくつか存在する。僕がこのようなものを必要としているのは、我が家は固定 IP ではなく、時々 IP が変化する(料金の安い)動的 IP でプロバイダーと契約しているからである。
我が家のサーバー hebe も同様なサービスを、家庭内のクライアントに対して行っている。例えば
meg% dial hebe:8007 192.168.0.6!8007 192.168.0.10!47686 meg% dial hebe:8008 1642213304 meg%のように、port 8007 に対してはサーバーとクライアントの IP と port を、port 8008 に対しては現在時刻(unix time)を知らせる。サーバー hebe の現在時刻はインターネットの時刻サービを参照しているので、実用上十分な精度を持っているはずである。家庭内の他のサーバーはこれを参照することになっている。
譜の dial.go
はいくつかの問題がある。
(a) IPv4 と IPv6 の選択ができない
(b) 柔軟性を欠いている
8000
にアクセスすると、"Hello World
" を表示するだけの、実用的価値のないサーバーである。package main import ( . "fmt" "os" "net" "io" ) var usage = "usage: srv1" func Error(s interface{}){ Fprintln(os.Stderr,s) os.Exit(1) } //https://pkg.go.dev/net#example-Listener func handleConn(conn net.Conn){ _, err:= io.WriteString(conn,"Hello World\n") if err != nil { Fprint(os.Stderr,"#Error ",err) } conn.Close() } func main() { ln, err := net.Listen("tcp", ":8000") if err != nil { Error(err) } for { conn, err := ln.Accept() if err != nil { Error(err) } //Fprintf(os.Stderr,"%T\n",conn) // *net.TCPConn go handleConn(conn) } }
譜 srv1.go
ln.Accept()
の所で for
ループになっているのは、クライアントから何回でもアクセスされるからである。
解せない箇所がある。ln.Accept()
が返す conn
の型を調べると *net.TCPConn
となっている。ところが
func handleConn(conn *net.TCPConn){
conn net.Conn
でなくてはならないと。
srv1
は次のように実行される。
meg% go build srv1.go meg% srv1& meg% dial meg:8000 Hello World meg%
今の場合には handleConn
の負荷が小さい。そして dial
は意地悪をしていない。このケースでは
go handleConn(conn)
handleConn(conn)
handleConn
が直ちに終了する保証はない。go
を付加したのは handleConn(conn)
を接続ごとに並列的に動作させたいからである。内部では C のfork()
関数あるいは thread が使われているはずである。これを Goroutine と言う。
今度は srv2.go
として、アクセスしたときに、ホスト側のアドレスとポート、およびクライアント側のアドレスとポートを教えてくれるサーバーを作成する。srv1.go
からの変更点はただ一つ
_, err:= io.WriteString(conn,"Hello World\n")
_, err:= io.WriteString(conn,Sprint(conn.LocalAddr(),"\n", conn.RemoteAddr(),"\n"))
僕のように C に慣れきっている人間は、ここで手が止まってしまう。Sprint
が生成したメモリーはどうなるのだ? このメモリーを開放できないではないか? と。C 言語であればアウトである。しかし Go はゴミ取りをやってくれる。ゴミ取りが正しく働いていれば WriteString
が終了した段階でメモリーを開放してくれるはずである。Go を信頼しましょう。
最後に srv3.go
として、現在の unix time を教えてくれるサーバープログラムを作成する。srv1.go
との変更点は次の2点:
_, err:= io.WriteString(conn,"Hello World\n")
_, err:= io.WriteString(conn,Sprint(time.Now().Unix()))
import
の中に "time"
を含めること
補注: srv2.go
のプログラム、なぜか Plan9 では動作が変である。Go のバグの可能性が高い。Linux では問題ない。なお Plan9 で同じことを行うには、数行のシェルスクリプトで足りる。ファイル t1
を
#!/bin/rc cat $net/local cat $net/remoteとして
aux/listen1 'tcp!*!8000' t1 &
[1] Documentation
https://pkg.go.dev/net
package main import ( . "fmt" "os" "io" "net/http" ) var usage = "usage: hget1 url" func Error(s interface{}){ Fprintln(os.Stderr,s) os.Exit(1) } func main() { args := os.Args if len(args) != 2 { Error(usage) } resp,err := http.Get(args[1]) if err != nil { Error(err) } defer resp.Body.Close() body,err := io.ReadAll(resp.Body) if err != nil { Error(err) } Print(string(body)) }
譜 hget1.go
詳しくは文献[1]を見よ。
hget1
にしたのは Plan9 には既に hget
があるから
hget
よりも丁寧なエラーメッセージを出している
https://pkg.go.dev/net/http
からの引用:
次に基礎的な Web サーバーのコードを示す。
package main import ( . "fmt" "os" "net/http" ) var usage = "usage: hsrv1 dir [port]" func Error(s interface{}){ Fprintln(os.Stderr,s) os.Exit(1) } /* Ref: $GOROOT/src/net/http/example_test.go:60 for this code */ func main() { var port = "80" // Simple static webserver: args := os.Args[1:] if len(args) == 0 || len(args) > 2 { Error(usage) } dir := args[0] if len(args) == 2 { port = args[1] } err := http.ListenAndServe(":"+port, http.FileServer(http.Dir(dir))) if err != nil { Fprintln(os.Stderr, err); } }
譜 hsrv1.go
文献[1]と[2]を参考にしたが、いくつか修正されている。修正点は
文献[2]では
log.Fatal(http.ListenAndServe(":8080", http.FileServer(http.Dir("/usr/share/doc"))))
"/usr/share/doc"
を起動時に引数として与えるようにしたのと、log.Fatal
は止めて、直接 err
の内容を見れるようにしただけである。log.Fatal
を使いたいならif err != nil { Fprintln(os.Stderr, err); log.Fatal(err) }とすれば良い。
Go の開発者たちはセキュリティに敏感である。指定された document root からアクセスがはみ出さないように細心の注意が払われていると想像する。(彼らの開発した Plan9 の Web サーバーのコードを見ると、そうであった)
このサーバーでは
/index.html
" を追加する.
" で始まるファイルは隠しファイルである。
問題点:
CGI をサポートしたい場合には次のようになる:
package main import ( . "fmt" "os" "io" "net/http" "html" bio "bufio" ) type typeless interface{} var usage = "usage: hsrv2 dir [port]" func Error(s interface{}){ Fprintln(os.Stderr,s) os.Exit(1) } func fooHandler(w http.ResponseWriter, r *http.Request) { io.WriteString(w, "こんにちは!\n") } func barHandler(w http.ResponseWriter, r *http.Request) { Fprintf(w, "Hello, %q\n", html.EscapeString(r.URL.Path)) } var dir = "" /* look https://pkg.go.dev/net/http $GOROOT/src/net/http/example_handle_test.go $GOROOT/src/net/http/example_test.go:68 for http.Handle and http.HandleFunc */ func main() { var port = "80" args := os.Args[1:] if len(args) == 0 || len(args) > 2 { Error(usage) } dir = args[0] // document root if len(args) == 2 { port = args[1] } if dir[0] != '/' { Error(`need dir to start with "/"`) } if len(dir) == 1 { Error(`dir must not be "/"`) } http.HandleFunc("/foo", fooHandler) http.HandleFunc("/bar", barHandler) http.Handle("/", http.FileServer(http.Dir(dir))) err := http.ListenAndServe(":"+port, nil) if err != nil { Fprintln(os.Stderr, err); } }
譜 hsrv2.go
Handler
は用途に応じて各自コードを修正すること。
https://pkg.go.dev/net/http
[2] $GOROOT/src/net/http/example_test.go
package main import ( . "fmt" "flag" "os" "io" "net/http" "html" ) type typeless interface{} var usage = "usage: hsrv3 -c cert.pem -k key.pem dir [port]" func Error(s interface{}){ Fprintln(os.Stderr,s) os.Exit(1) } func fooHandler(w http.ResponseWriter, r *http.Request) { io.WriteString(w, "こんにちは!\n") } func barHandler(w http.ResponseWriter, r *http.Request) { Fprintf(w, "Hello, %q\n", html.EscapeString(r.URL.Path)) } var dir = "" func main() { var port = "443" cert := flag.String("c","","cert.pem") key := flag.String("k","","key.pem") flag.Parse() if *cert == "" || *key == "" { Error(usage) } args := flag.Args() if len(args) == 0 || len(args) > 2 { Error(usage) } dir = args[0] // document root if len(args) == 2 { port = args[1] } if dir[0] != '/' { Error(`need dir to start with "/"`) } if len(dir) == 1 { Error(`dir must not be "/"`) } http.HandleFunc("/foo", fooHandler) http.HandleFunc("/bar", barHandler) http.Handle("/", http.FileServer(http.Dir(dir))) err := http.ListenAndServeTLS(":"+port, *cert, *key, nil) if err != nil { Fprintln(os.Stderr, err); } } /* example in one window hsrv3 -c cert.pem -k key.pem $home/www/doc 8443 in another window hget2 -f https://meg.local:8443 */
譜 hsrv3.go
実行例はコードの中にコメントとして与えられている。meg.local
は実験用のサーバーである。cert.pem
と key.pem
の作り方は次の Certificate に示す。
自己証明書は次のように作成できる。
gcert=$GOROOT/src/crypto/tls/generate_cert.go go run $gcert -host meg.local
譜 gencert
ここに meg.local
はサーバーのドメイン名で、各自の環境に応じて設定する。
何回も実行する必要がないので、go run
で充分だろう。
Ref:
https://go.dev/src/crypto/tls/generate_cert.go
今度の hget は、相手が自己証明の場合に対応している。force flag "-f
" がそれである。このフラグを与えてない場合には hget1.go
と同じ動作になるはずである。
package main import ( . "fmt" "os" "io" "net/http" "crypto/tls" ) var usage = "usage: hget2 [-f] url" func Error(s interface{}){ Fprintln(os.Stderr,s) os.Exit(1) } func main() { var force = false args := os.Args[1:] if args[0] == "-f" { force = true args = args[1:] } if len(args) != 1 { Error(usage) } tr := &http.Transport{ TLSClientConfig: &tls.Config{InsecureSkipVerify: force}, } client := &http.Client{Transport: tr} resp, err := client.Get(args[0]) if err != nil { Error(err) } defer resp.Body.Close() body,err := io.ReadAll(resp.Body) if err != nil { Error(err) } Print(string(body)) }
譜 hget2.go
通信のトランスポート層に対する制御は
tr := &http.Transport{ TLSClientConfig: &tls.Config{InsecureSkipVerify: force}, }
次は hget2.go
を作成するにあたって、僕が調べたことのメモ。
for http.Client look
https://github.com/golang/go/blob/master/src/net/http/transport.go
type Transport struct { ... TLSClientConfig *tls.Config ... }
and in https://pkg.go.dev/crypto/tls,
we have:
type Config struct { ... InsecureSkipVerify bool ... }
コードを書くにあたって、記事[2]が参考になった。
https://stackoverflow.com/questions/12122159/how-to-do-a-https-request-with-bad-certificate
基になっている文献は[1]にある。
https://pkg.go.dev/net/http#Client.Get
[2] How to get x509 Certificate from http Client in Go
https://stackoverflow.com/questions/12122159/how-to-do-a-https-request-with-bad-certificate