多値と多重代入

すでにさらっと書いたけど、Go の関数は複数の値を返すことができる。いわゆる「多値」ってやつ。で、それを受け取るほうも普通に多重代入ができる。例を示すほうが早いだろう。

package main

import "fmt"

func divMod(x, y int) (int, int) {
    return x / y, x % y
}

func main() {
    p, q := divMod(10, 3)
    fmt.Println(p)
    fmt.Println(q)
}
^o^ > go run multivalue.go
3
1

見てわかるように、多値を返すには関数の返り値の型をカッコでくくって並べて書き、return に同じ数の値(もちろん型があってなきゃいけない)を渡してやるだけだ。Scheme の多値よりも扱いが簡単だね。

さて、多重代入ができるってことはこんなこともできる。

package main

import "fmt"

func main() {
    a, b := 10, 20
    fmt.Println(a, b)

    b, a = a, b
    fmt.Println(a, b)
}
^o^ > go run swap.go
10 20
20 10

ローカル変数のスコープ

関数は変数のスコープを作るので、引数や関数内で宣言された変数はその関数内だけで有効なローカル変数になる。
ほかにも {} でブロックを作ってやると、そのブロックは新しいスコープを作ってブロック内で宣言された変数はブロック内だけで有効になる。例えば、次の例がわかりやすいだろう。

package main

import "fmt"

func main() {
    x := 1
    {
        y := 2
        {
            z := 3
            fmt.Println(x)
            fmt.Println(y)
            fmt.Println(z)
        }
        fmt.Println(x)
        fmt.Println(y)
    // fmt.Println(z) z はスコープ外(コンパイルエラー)
    }
    fmt.Println(x)
    // fmt.Println(y) y はスコープ外(コンパイルエラー)
    // fmt.Println(z) z はスコープ外(コンパイルエラー)
}

注意が必要なのは、if 文なども新しいスコープを作るので、次の例はエラーになってしまう。

package main

import "fmt"

func main() {
    a := 10

    if a > 5 {
        b := "Yes"
    } else {
        b := "No"
    }

    fmt.Println(b)
}
^o^ > go run var_local2.go
# command-line-arguments
.\var_local2.go:14: undefined: b

b を出力しようとしているところで、b が定義されていないと怒られている。これを避けるためには、先に b を宣言しておく。

package main

import "fmt"

func main() {
    a := 10
    var b string

    if a > 5 {
        b = "Yes"
    } else {
        b = "No"
    }

    fmt.Println(b)
}
^o^ > go run var_local3.go
Yes

マップ

Go のマップは Ruby でいうハッシュ、Python の辞書、要するに連想配列だ。
マップの宣言、初期化は例を見たほうが早いだろう。次のようにする。

package main

import "fmt"

func main() {
    var a map[string]int
    var b map[string]int = map[string]int{ "foo": 1, "bar": 2 }
    var c = map[string]int{ "hoge": 10, "fuga": 20 }

    fmt.Println(a)
    fmt.Println(b)
    fmt.Println(c)
}
^o^ > go run map.go
map[]
map[foo:1 bar:2]
map[hoge:10 fuga:20]

ここで注意。上の例の変数 a のように宣言だけして初期化をしないと、nilマップというものになる。これは空のマップとは違って、キーの追加とかができない。試してみよう。

package main

import "fmt"

func main() {
    var a map[string]int

    fmt.Println(a)

    a["hoge"] = 100

    fmt.Println(a)
}
^o^ > go run map2.go
map[]
panic: assignment to entry in nil map

goroutine 1 [running]:
main.main()
        C:/Users/takatoh/Documents/w/learning-go/map2.go:10 +0xa7
exit status 2

この通り、a["hoge"] = 100 のところでエラーを起こしている。なんでこんな仕様になってるんだろ?
で、空のマップがほしい時には make 関数を使う。

package main

import "fmt"

func main() {
    var a = make(map[string]int)

    fmt.Println(a)

    a["foo"] = 10
    a["bar"] = 20

    fmt.Println(a)
}

これなら新しいキー(と値)を追加できる。

^o^ > go run map3.go
map[]
map[foo:10 bar:20]

参照するには [] でキーを指定すればいいけど、返ってくる値は2つある。キーが存在する場合には対応する値と true が、存在しない場合にはゼロ値と false が返ってくる。これはつまり Go には多値があるってことだけど、これについては別の機会に書く。
ともかく、キーが存在するかどうかは2つ目の返り値を見ないとわからないので、ちょっと面倒。
あと、キー(と値)を削除するには delete 関数を使う。

package main

import "fmt"

func main() {
    a := map[string]int{ "foo": 1, "bar": 2 }

    fmt.Println(a)

    value, ok := a["foo"]
    if ok {
        fmt.Println(value)
    }

    a["baz"] = 3
    delete(a, "bar")

    fmt.Println(a)
}
^o^ > go run map4.go
map[foo:1 bar:2]
1
map[baz:3 foo:1]

FTPサーバ(vsftpd)の設定

さくらで借りている VPS に FTP サーバ(vsftpd)を設定した記録。

環境

  • CentOS 6.9
  • x86_64

方針

  • ASCII転送はなし
  • 特定のユーザしかアクセスさせない
  • ホームディレクトリのpublic以下しかアクセスさせない
  • アップロードしたファイルにユーザ以外の書き込み権限を付与しない

FTP用ユーザの作成

[takatoh@tk2-254-36564 ~]$ sudo useradd ftpuser
[takatoh@tk2-254-36564 ~]$ sudo passwd ftpuser
ユーザー ftpuser のパスワードを変更。
新しいパスワード:
新しいパスワードを再入力してください:
passwd: 全ての認証トークンが正しく更新できました。

ftpuser のホームディレクトリに public ディレクトリを作る。

[takatoh@tk2-254-36564 ~]$ ls -l /home
合計 8
drwx------ 2 ftpuser ftpuser 4096 10月 15 07:37 2017 ftpuser
drwx------ 5 takatoh takatoh 4096 10月  5 21:54 2017 takatoh
[takatoh@tk2-254-36564 ~]$ sudo mkdir -p /home/ftpuser/public
[takatoh@tk2-254-36564 ~]$ sudo ls -l /home/ftpuser
合計 4
drwxr-xr-x 2 root root 4096 10月 15 07:41 2017 public
[takatoh@tk2-254-36564 ~]$ sudo chown ftpuser:ftpuser /home/ftpuser/public
[takatoh@tk2-254-36564 ~]$ sudo ls -l /home/ftpuser
合計 4
drwxr-xr-x 2 ftpuser ftpuser 4096 10月 15 07:41 2017 public

インストール

[takatoh@tk2-254-36564 ~]$ sudo yum install vsftpd

設定ファイル /etc/vsftpd/vsftpd.conf の設定

変更した点、重要な点だけ書く。

[takatoh@tk2-254-36564 ~]$ cd /etc/vsftpd
[takatoh@tk2-254-36564 vsftpd]$ ls
ftpusers  user_list  vsftpd.conf  vsftpd_conf_migrate.sh
[takatoh@tk2-254-36564 vsftpd]$ sudo cp vsftpd.conf vsftpd.conf.orig
[takatoh@tk2-254-36564 vsftpd]$ sudo vim vsftpd.conf

anonymousログインを許可しない。NO に変更。

anonymous_enable=NO

ローカルユーザのログインを許可する設定。そのまま。

local_enable=YES

anonymousログインでの書き込みを許可しない。コメントのまま。

#anon_upload_enable=YES

anonymousログインでのディレクトリ作成を許可しない。コメントのまま。

#anon_mkdir_write_enable=YES

ログファイルにアップロードとダウンロードの詳細を記録する。そのまま。

xferlog_enable=YES

データ接続でサーバがポート20を送信元にする。そのまま。

connect_from_port_20=YES

ログファイルのファイル名。デフォルトでよいのでコメントのまま。

#xferlog_file=/var/log/xferlog

ログをxferlogのフォーマットにする。そのまま。

xferlog_std_format=YES

アップロード、ダウンロードでの ASCII 転送を認めないのでコメントのまま。

#ascii_upload_enable=YES
#ascii_download_enable=YES

ローカルユーザがログイン後にホームディレクトリに chroot するかの設定。コメントを外して有効にする。

chroot_local_user=YES

chroot_listを有効にする。chroot_local_user の設定で意味が変わる。YES の場合、chroot_list_file は chroot しないユーザを指定する。コメントを外して有効にする。

chroot_list_enable=YES

chroot_local_user を有効にしたので、chroot_list_file が無いとアクセスできない。コメントを外して有効にする。

chroot_list_file=/etc/vsftpd/chroot_list

スタンドアローン(デーモン)として起動するかの設定。そのまま。

listen=YES

ここからはデフォルトの設定ファイルにないので追記。
userlist_deny を NO にするとアクセスを許可するユーザーのリストになる。NO にする。

userlist_deny=NO

ユーザリストのファイルを指定。

userlist_file=/etc/vsftpd/user_list

ログイン後に移動するディレクトリを指定。chrootが有効になっている場合、chroot後のルートを指定する。

local_root=public

vsftpd.confの設定は終了。

chroot_local_user、chroot_list_enable の両方をYESにしたので、chroot_list_fileが無いとログインを拒否される。空のファイルを作る。

[takatoh@tk2-254-36564 vsftpd]$ sudo touch /etc/vsftpd/chroot_list

デフォルトでは /etc/vsftpd/user_list がアクセスを拒絶するリストになっている。許可するファイルに編集し直す。書いてあるユーザ名をすべてコメントアウトして ftpuser を追加する。

[takatoh@tk2-254-36564 vsftpd]$ sudo vim /etc/vsftpd/user_list

iptablesの設定

ftpのデータ用ポートのためのモジュールを読み込むように設定する。

[takatoh@tk2-254-36564 vsftpd]$ cd /etc/sysconfig
[takatoh@tk2-254-36564 sysconfig]$ sudo vim iptables-config
IPTABLES_MODULES="ip_conntrack_ftp"

ポート21への接続を許可するようにする。

[takatoh@tk2-254-36564 sysconfig]$ sudo vim iptables

つぎの行を追加。state ESTABLISHED,RELATEDとREJECTの間に追加する。

-A INPUT -m state --state NEW -m tcp -p tcp --dport 21 -j ACCEPT

iptables を再起動。

[takatoh@tk2-254-36564 sysconfig]$ sudo service iptables restart
iptables: Setting chains to policy ACCEPT: filter          [  OK  ]
iptables: Flushing firewall rules:                         [  OK  ]
iptables: Unloading modules:                               [  OK  ]
iptables: Applying firewall rules:                         [  OK  ]
iptables: Loading additional modules: ip_conntrack_ftp     [  OK  ]

FTPサーバ(デーモン)の起動

[takatoh@tk2-254-36564 ~]$ sudo service vsftpd start
Starting vsftpd for vsftpd:                                [  OK  ]

OS起動時にvsftpdも起動するようにする。

[takatoh@tk2-254-36564 ~]$ sudo chkconfig vsftpd on
[takatoh@tk2-254-36564 ~]$ sudo chkconfig --list vsftpd
vsftpd         	0:off	1:off	2:on	3:on	4:on	5:on	6:off

これでインストールと設定は終了。

テスト

ローカルから接続してファイルをアップロードしてみる。

takatoh@envelopes $ ftp ftp.lathercraft.net
Connected to lathercraft.net.
220 (vsFTPd 2.2.2)
Name (ftp.lathercraft.net:takatoh): ftpuser
331 Please specify the password.
Password:
230 Login successful.
Remote system type is UNIX.
Using binary mode to transfer files.
ftp> ls
200 PORT command successful. Consider using PASV.
150 Here comes the directory listing.
226 Directory send OK.
ftp> put example.zip
local: example.zip remote: example.zip
200 PORT command successful. Consider using PASV.
150 Ok to send data.
226 Transfer complete.
300012340 bytes sent in 26.17 secs (10.9330 MB/s)
ftp> ls
200 PORT command successful. Consider using PASV.
150 Here comes the directory listing.
-rw-r--r--    1 501      502      300012340 Oct 14 23:47 example.zip
226 Directory send OK.
ftp> bye
221 Goodbye.

うまくいったようだ。
試しに wget でダウンロードしてみたけど、これもうまく行った。

参考ページ

cf. ftpサーバ(vsftpd)の設定

スライス(2)

スライスは make 関数を使っても生成できる。

変数名 := make([]型, 大きさ, 容量)

「大きさ」と「容量」があるけど、「大きさ」は要素の数(ここではゼロ値に初期化される)、「容量」はメモリ上に確保される領域のサイズのこと、と言ったらいいかな。「容量」は省略できる。
「大きさ」、「容量」はそれぞれ len 関数、cap 関数で得ることができる。

package main

import "fmt"

func main() {
    a := make([]int, 5, 10)

    fmt.Println(a)
    fmt.Println(len(a))
    fmt.Println(cap(a))
}
^o^ > go run slice4.go
[0 0 0 0 0]
5
10

スライスは、append 関数を使って末尾に要素を追加できる。

package main

import "fmt"

func main() {
    a := make([]int, 0)
    fmt.Println(a)

    for i := 0; i < 5; i++ {
        a = append(a, i)
        fmt.Println(a)
    }
}
^o^ > go run slice5.go
[]
[0]
[0 1]
[0 1 2]
[0 1 2 3]
[0 1 2 3 4]

容量を超えて要素を追加しようとすると、スライスはよりを大きい容量の新しいメモリ領域を確保する。これは自動的に行われる。ちょっと試してみよう。

package main

import "fmt"

func main() {
    a := make([]int, 0)

    fmt.Printf("len=%d cap=%d %p %v\n", len(a), cap(a), a, a)

    for i := 1; i < 18; i++ {
        a = append(a, i)
        fmt.Printf("len=%d cap=%d %p %v\n", len(a), cap(a), a, a)
    }
}
^o^ > go run slice6.go
len=0 cap=0 0x526b88 []
len=1 cap=1 0xc0420381d8 [1]
len=2 cap=2 0xc042038220 [1 2]
len=3 cap=4 0xc04203e500 [1 2 3]
len=4 cap=4 0xc04203e500 [1 2 3 4]
len=5 cap=8 0xc042040140 [1 2 3 4 5]
len=6 cap=8 0xc042040140 [1 2 3 4 5 6]
len=7 cap=8 0xc042040140 [1 2 3 4 5 6 7]
len=8 cap=8 0xc042040140 [1 2 3 4 5 6 7 8]
len=9 cap=16 0xc042052180 [1 2 3 4 5 6 7 8 9]
len=10 cap=16 0xc042052180 [1 2 3 4 5 6 7 8 9 10]
len=11 cap=16 0xc042052180 [1 2 3 4 5 6 7 8 9 10 11]
len=12 cap=16 0xc042052180 [1 2 3 4 5 6 7 8 9 10 11 12]
len=13 cap=16 0xc042052180 [1 2 3 4 5 6 7 8 9 10 11 12 13]
len=14 cap=16 0xc042052180 [1 2 3 4 5 6 7 8 9 10 11 12 13 14]
len=15 cap=16 0xc042052180 [1 2 3 4 5 6 7 8 9 10 11 12 13 14 15]
len=16 cap=16 0xc042052180 [1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16]
len=17 cap=32 0xc042032100 [1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17]

%p は変数のポインタを表す書式指定子。上の例を見ると、大きさ(len)が容量(cap)を超えるタイミングで容量も大きくなり、同時にポインタの値も変化していく様子が見て取れる。

スライス

スライスは配列に似ているが、要素を追加することによって大きさ(長さ)を変更できるデータ構造。大きさの変更は自動的になされる。小さくすることはできない。たぶん。

スライスの宣言は次のように大きさを指定せずに行う。

var 変数名 []型

同時に初期化するには:

var 変数名 []型 = []型{ 値1, 値2, 値3, ... }
var 変数名 = []型{ 値1, 値2, 値3, ... }

大きさを指定しないことを除けば、配列と一緒だな。ちょっと試してみよう。

package main

import "fmt"

func main() {
    var s1 []int
    var s2 []int = []int{ 1,2,3,4,5 }
    var s3 = []int{ 6,7,8,9,10 }

    fmt.Println(s1)
    fmt.Println(s2)
    fmt.Println(s3)
}
^o^ > go run slice.go
[]
[1 2 3 4 5]
[6 7 8 9 10]

スライスはまた、配列の部分列を取り出すことでも作れる。

操作 意味
s[m:n] m から n – 1 まで
s[m:] m から最後尾まで
s[:n] 先頭から n – 1 まで
s[:] 先頭から最後尾まで
package main

import "fmt"

func main() {
    var array = [8]int{ 1,2,3,4,5,6,7,8 }

    var s1 = array[2:6]
    var s2 = array[3:]
    var s3 = array[:3]
    var s4 = array[:]

    fmt.Println(s1)
    fmt.Println(s2)
    fmt.Println(s3)
    fmt.Println(s4)
}
^o^ > go run slice2.go
[3 4 5 6]
[4 5 6 7 8]
[1 2 3]
[1 2 3 4 5 6 7 8]

スライスを配列の部分列として取り出した場合、元の配列とデータ格納領域を共有することに注意。つまり、スライスの要素を変更すると、元の配列の対応する要素も変更される。

package main

import "fmt"

func main() {
    var array = [4]int{ 1,2,3,4 }

    fmt.Println(array)

    var s = array[:]
    s[2] = 10

    fmt.Println(s)
    fmt.Println(array)
}
^o^ > go run slice3.go
[1 2 3 4]
[1 2 10 4]
[1 2 10 4]

配列

配列は次のように大きさ(長さ)と要素の型を合わせて宣言する。

var 変数名 [大きさ]型

要素の型が決まっているので、同じ配列に異なる型の要素を格納することはできない。
また、大きさも含めて配列の型なので、要素の型が同じでも大きさが異なれば別の型とみなされる。例えば [4]int[8]int は別の型だ。

宣言と同時に初期化するには次のようにする。

var 変数名 [大きさ]型 = [大きさ]型{ 値0, 値1, 値2, 値3, ... }

または

var 変数名 = [大きさ]型{ 値0, 値1, 値2, 値3, ... }

初期化しないと、配列の要素はゼロ値になる。

少し試してみよう。

package main

import "fmt"

func main() {
    var a [4]int
    var b [6]int = [6]int{ 0, 1, 2, 3, 4, 5 }
    var c = [8]int { 0, 1, 2, 3, 4, 5, 6, 7 }

    fmt.Println(a)
    fmt.Println(b)
    fmt.Println(c)
}
o^ > go run array.go
[0 0 0 0]
[0 1 2 3 4 5]
[0 1 2 3 4 5 6 7]

配列の要素にアクセスするには、[] を使ってインデックスを指定する。インデックスは 0 始まりだ。

package main

import "fmt"

func main() {
    var a [4]int = [4]int{ 1, 2, 3, 4 }

    fmt.Println(a[2])
    fmt.Println(a)

    a[1] = 7

    fmt.Println(a[1])
    fmt.Println(a)
}
^o^ > go run array2.go
3
[1 2 3 4]
7
[1 7 3 4]

最後に len 関数について書いておこう。len 関数は配列の大きさ(長さ)を返す。

package main

import "fmt"

func main() {
    var a [4]int = [4]int{ 1, 2, 3, 4 }

    fmt.Println(a)
    fmt.Println(len(a))
}
^o^ > go run array3.go
[1 2 3 4]
4

switch文

条件分岐には if 文のほかにもうひとつ、switch 文がある。switch 文は次の通り。

switch 条件式 {
    case 式A:
        処理A1
        処理A2
        処理A3
    case 式B:
        処理B1
        処理B2
        処理B3
    case 式C:
        処理C1
        処理C2
        処理C3
    default:
        処理Z1
        処理Z2
        処理Z3
}

条件式の値と case 節の式A、B、C の値が順に比較され、等しくなる最初の case 節の処理が実行される。どの case 節の式とも等しくなければ default 節が実行される。
試してみよう。お題は FizzBuzz 問題だ。

package main

import "fmt"

func main() {
    i := 1

    for i <= 20 {
        switch i % 15 {
            case 0:
                fmt.Println("FizzBuzz")
                i++
            case 3, 6, 9, 12:
                fmt.Println("Fizz")
                i++
            case 5, 10:
                fmt.Println("Buzz")
                i++
            default:
                fmt.Println(i)
                i++
        }
    }
}
^o^ > go run switch.go
1
2
Fizz
4
Buzz
Fizz
7
8
Fizz
Buzz
11
Fizz
13
14
FizzBuzz
16
17
Fizz
19
Buzz

例題のために冗長な書き方にしたけど、期待通り動いている。見ればわかるように、case 節の式はひとつでなくてもいい。
また、条件式を書かない書き方もある。この場合には case 節の式の値が true になる最初の節が実行される。

package main

import "fmt"

func main() {
    i := 1

    for i <= 20 {
        switch {
            case i % 15 == 0:
                fmt.Println("FizzBuzz")
            case i % 3 == 0:
                fmt.Println("Fizz")
            case i % 5 == 0:
                fmt.Println("Buzz")
            default:
                fmt.Println(i)
        }
        i++
    }
}
^o^ > go run switch2.go
1
2
Fizz
4
Buzz
Fizz
7
8
Fizz
Buzz
11
Fizz
13
14
FizzBuzz
16
17
Fizz
19
Buzz

無限ループとbreak、continue

for 文で、条件部を省略してただ for と書くと無限ループになる。

for {
    処理
}

ループの制御として、breakcontinue がある。break はループを抜け、continue はループの頭に戻る。
試してみよう。

package main

import "fmt"

func main() {
    i := 0

    for {
        if i > 10 {
            break
        } else if i % 3 == 0 {
            i++
            continue
        } else {
             fmt.Println(i)
        }
        i++
    }
}
^o^ > go run for_inf.go
1
2
4
5
7
8
10