root でログインする。ログイン画面の、「アカウントが見つかりませんか?」を選ぶとユーザー名を入力できるようになる。この画面で root でログインする。
左上の「アプリケーション」と書いてあるところをクリックして端末を起動。yum update
コマンドでアップデート。
[root@aybesea ~]# yum update
300近いパッケージがアップデートされた。
takatoh's blog – Learning programming languages.
root でログインする。ログイン画面の、「アカウントが見つかりませんか?」を選ぶとユーザー名を入力できるようになる。この画面で root でログインする。
左上の「アプリケーション」と書いてあるところをクリックして端末を起動。yum update
コマンドでアップデート。
[root@aybesea ~]# yum update
300近いパッケージがアップデートされた。
というわけで、今日は買い替えた古いほうのマシンに CentOS 7 をインストールする。
まずはインストールメディア(DVD)を作るところから。ダウンロードのページから DVD ISO イメージをダウンロードして、昨日セットアップした Windows マシン(montana)で DVD に焼いた。
さて、DVD を入れて PC を起動するとインストールメニューが出るので、「Test this media & install CentOS 7」を選ぶ。するとメディアのチェックの後、インストールが始まる。
最初は言語の設定から。インストーラは GUI になっててわかりやすい。
つぎに、インストールの概要の画面になったけど、ネットワークが接続されていない。これはちょっと気になる。設定をオンにするとつながるようになった。あと、ここでホスト名も設定する。aybesea にした。インストール先は「自動パーティション設定が選択されました」となっているのでこれはそのまま。ソフトウェアの選択も「最小限のインストール」のままとした。それから SECURITY POLICY もデフォルトのまま。これでインストール開始。
インストール中に、root のパスワードを設定。ユーザーは takatoh を作成した。
しばらくするとインストールが終了して再起動を促される。素直に再起動。ログインの画面になるけど、root でログインしようとしてもできない。なぜだ。パスワードを間違えたか?
仕方がないので、インストールの初めからやり直し。設定はさっきとほぼ一緒だけど、ソフトウェアの選択で「サーバー(GUI使用)」を選んでみたのと、ユーザーの作成をしなかったところが違う。今度はさっきよりだいぶ時間がかかる。
インストールが終わって、再起動すると GUI の画面になる。まずはライセンスに同意。そしてユーザーの作成。takatoh を作成した。設定の完了ボタンをクリック。
画面が切り替わってログイン画面になる。takatoh があるので、パスワードを入力してログイン。root はなかったな。ログインすると簡単な設定があって、デフォルトかスキップで OK。
とりあえずここまで。
参考書を買った。
昨日の話。Windowsマシン valarie の買い替えの新しい PC のセットアップをした。Windows の更新にすげー時間がかかったせいで1日仕事だった。
セットアップ自体は特に難しいところはないんだけど、途中で Windows の更新をするところで数時間もかかってしまった。特に、「完了の割合」が 99% になってからずっと2時間くらい進まず、結局お祈りしながら強制リブートした。無事リブートしたのでよしとする。更新がどうなったかは分からないけど、今日になってみたら新しい更新がインストールされていたので、結果としては大丈夫だったようだ。
以下、インストールしたアプリケーション。
とりあえずこんなところ。
あ、ホスト名は montana にした。
あと古いマシンは廃棄する前に CentOS を入れていろいろ実験してみるつもり。
ゴルーチンは、Go で並行プログラミングを実現する機能だ。Elixir のプロセスと同じようなものだと理解した。
ゴルーチンを使うには次のように関数呼び出しの前に go
をつけるだけだ。これでその関数は新しいゴルーチンの中で実行され、プログラムはゴルーチンの終了を待つことなく次の処理に移る。つまりゴルーチンの処理とメインの処理が平行に動作するってわけだ。
例を見てみよう。まずは普通の(ゴルーチンを使わない)プログラム。
package main import ( "fmt" "time" ) func test(n int, name string) { for i := 1; i <= n; i++ { fmt.Println(i, name) time.Sleep(500 * time.Millisecond) } } func main() { test(5, "foo") test(5, "bar") }
2度呼び出されている test
関数は、それぞれ “foo” と “bar” を5回ずつ出力する。これは当然書いてある順に処理される。
^o^ > go run go_name.go 1 foo 2 foo 3 foo 4 foo 5 foo 1 bar 2 bar 3 bar 4 bar 5 bar
じゃあ、次はゴルーチンを使ってみよう。ひとつめの関数呼び出しをゴルーチンに渡してみる。こんなプログラムになる。
package main import ( "fmt" "time" ) func test(n int, name string) { for i := 1; i <= n; i++ { fmt.Println(i, name) time.Sleep(500 * time.Millisecond) } } func main() { go test(5, "foo") test(5, "bar") }
実行してみる。
^o^ > go run go_name2.go 1 bar 1 foo 2 bar 2 foo 3 bar 3 foo 4 bar 4 foo 5 bar 5 foo
“foo” と “bar” が交互に出力され、二つの関数呼び出しが平行に動作している様子がわかる。
ちなみに、メインのプログラムが終了するとゴルーチンも終了するので、2つの関数呼び出しを両方ともゴルーチンにしてしまうと、何も出力されなくなる。
ググると、Ubuntu の公式パッケージにある Go は 1.6 と古いので非公式のリポジトリを登録しろ、という情報があるんだけど、apt search golang
してみたら golang-1.9 というパッケージがあった。なので、これをインストールする。
takatoh@envelopes $ sudo apt install golang-1.9
ところが go version
コマンドを実行しても、インストールされてない、apt install golang-go
をしろ、と言われる。素直にそうしてみると、今度は Go 1.6 がインストールされてしまった。
調べてみると、/usr/lib の下に golang-1.9 と golang-1.6 があって、/usr/bin/go から 1.6 の方へリンクがはられている。ということはこのリンクを 1.9 の方へはりなおしてやればいいはず。いったん 1.6 をアンインストールしてから、リンクをはりなおした。
takatoh@envelopes $ sudo ln -s /usr/lib/go-1.9/bin/go /usr/bin/go takatoh@envelopes $ sudo ln -s /usr/lib/go-1.9/bin/gofmt /usr/bin/gofmt
これで無事完了。
takatoh@envelopes $ go version go version go1.9.2 linux/amd64
昨日の vsftpd が動かない問題、解決した。
話は2段階で進む。まずはじめに、vsftpd 自体が起動しない問題、これは listen
ディレクティブと listen_ipv6
ディレクティブが両方 YES
になっていたためだった。listen_ipv6
のほうをコメントアウトして解決。
listen=YES #listen_ipv6=YES
これで起動するようにはなったけど、アクセスしてみると「500 OOPS: vsftpd: refusing to run with writable root inside chroot()」というエラーが出てログインできない。これが2つめ。ググるとつぎのページが見つかった。
cf. vsftpdの設定で謎のエラーにハマった – TomoProgの技術書
どうやら chroot した先に書き込み権限があるとエラーになるらしい。allow_writeable_chroot
ディレクティブを追加して解決。
allow_writeable_chroot=YES
それにしても listen
と listen_ipv6
はデフォルトで両方 YES
になってた。デフォルトが動かない設定になってるってどういうことよ?
メインマシン envelopes (Ubuntu 16.04) と Windowns マシン valarie とのデータのやり取りのため、envelopes に vsftpd をインストールした。
takatoh@envelopes $ sudo apt install vsftpd
設定は、基本的には以前さくらの VPS に設定した時と同じ。ただし、ASCII 転送を有効にした。あと、設定ファイルは /etc/vsftpd.conf にあった。
# ASCII mangling is a horrible feature of the protocol. ascii_upload_enable=YES ascii_download_enable=YES
それから以下を追記。
userlist_deny=NO userlist_file=/etc/vsftpd.user_list local_root=Public
空の chroot_list ファイルを作る。
takatoh@envelopes $ sudo touch vsftpd.chroot_list
user_list ファイルを作る。
takatoh@envelopes $ sudo vim vsftpd.user_list
takatoh だけを指定した。
ポート 21 を開ける。
takatoh@envelopes $ sudo ufw allow 21/tcp ルールを追加しました ルールを追加しました (v6)
vsftpd をリスタート。
takatoh@envelopes $ sudo systemctl restart vsftpd
あれ?何もメッセージが出ないけどいいのかな?
takatoh@envelopes $ ps ax | grep vsftpd 18124 pts/18 S+ 0:00 grep --color=auto vsftpd
ああ、やっぱり動いてない。なんでだ……
www.panicblanket.com にホームページを作った。とはいっても2ページしか無いんだけど。
作成には Ruby 製静的サイトジェネレータの Middleman を使った。Middleman の使い方はググればいっぱい出てくるのでここでは省略。とりあえず日本語の公式サイトへのリンクだけ載せておこう。
cf. Middleman
基本的にはこの公式サイトの説明のとおりにやっていけば、サイトを作れる。というか作れた。
問題はサーバへのデプロイだ。デプロイは、要するにサーバの然るべきディレクトリにファイルをコピーすればいいので、FTP でも何でもいいんだけど、ファイル数が多くなればそれだけ面倒になる。で、公式サイトでは middleman-deploy というプラグインを使うといい、みたいなことが書いてあったのでそうすることにした。
ところがこれが罠だったわけだ。使い方は簡単、Gemfile に middleman-deploy を追加して、bundle install --path vendor/bundle
コマンドでインストール。config.rb にデプロイのための設定を書き足して(今回は FTP を使うことにした)、bundle exec middleman deploy
を実行するだけ。
で、実行したところこんなエラーが出た。
takatoh@envelopes $ bundle exec middleman deploy /home/takatoh/w/www.panicblanket.com/vendor/bundle/ruby/2.3.0/gems/middleman-core-4.2.1/lib/middleman-core/extensions.rb:96:in `load': Tried to activate old-style extension: deploy. They are no longer supported. (RuntimeError) from /home/takatoh/w/www.panicblanket.com/vendor/bundle/ruby/2.3.0/gems/middleman-core-4.2.1/lib/middleman-core/extensions.rb:127:in `block in load_settings' from /home/takatoh/w/www.panicblanket.com/vendor/bundle/ruby/2.3.0/gems/middleman-core-4.2.1/lib/middleman-core/extensions.rb:125:in `each' from /home/takatoh/w/www.panicblanket.com/vendor/bundle/ruby/2.3.0/gems/middleman-core-4.2.1/lib/middleman-core/extensions.rb:125:in `load_settings' from /home/takatoh/w/www.panicblanket.com/vendor/bundle/ruby/2.3.0/gems/middleman-core-4.2.1/lib/middleman-core/extension_manager.rb:12:in `initialize' from /home/takatoh/w/www.panicblanket.com/vendor/bundle/ruby/2.3.0/gems/middleman-core-4.2.1/lib/middleman-core/application.rb:263:in `new' from /home/takatoh/w/www.panicblanket.com/vendor/bundle/ruby/2.3.0/gems/middleman-core-4.2.1/lib/middleman-core/application.rb:263:in `initialize' from /home/takatoh/w/www.panicblanket.com/vendor/bundle/ruby/2.3.0/gems/middleman-cli-4.2.1/bin/middleman:49:in `new' from /home/takatoh/w/www.panicblanket.com/vendor/bundle/ruby/2.3.0/gems/middleman-cli-4.2.1/bin/middleman:49:in `' from /home/takatoh/w/www.panicblanket.com/vendor/bundle/ruby/2.3.0/bin/middleman:23:in `load' from /home/takatoh/w/www.panicblanket.com/vendor/bundle/ruby/2.3.0/bin/middleman:23:in ` '
長いけど、最初のところに、「Tried to activate old-style extension: deploy. They are no longer supported. (RuntimeError)」って書いてある。deploy は古いスタイルの拡張で、すでにサポートされていない、らしい。GitHub の middleman-deploy のページを見てみると、インストールしたバージョン 1.0 がリリースされたのが 2014 年。これ以降新しいバージョンのリリースがない。そりゃ古いよなぁ。でも公式サイトに書いてあるんだぜ?どうしろってんだ。
結局、FTP でファイルをひとつずつアップした。スタイルシートを含めても3ファイルしか無いので、大したことはなかったけど、これからファイル数が増えたらなんか考えなきゃいけない。みんなどうしてるんだろう?
本当はゴルーチンについて書きたいんだけど、まだ頭の中で整理ができてないので今日は別のことを書く。
Web サービスやなんかの API で JSON を返してくれるのはよくあること。ただ、基本的にプログラムが処理するように想定されていて、人間が見やすい形にはなっていないことが多い。まあ、当たり前ではあるんだけど、それでも目で見て確かめたいときもある。そういう時は見やすく整形してくれるツールがほしくなる。
ググると jq っていうツールが見つかる。このツール自体は、JSON の整形だけでなく検索とかいろいろできるようで、おまけに Windows 用のバイナリもあるのでちょっと使ってみた。
ところが!もとの JSON が UTF-8 のせいかもしれないけど、Windows のコマンドプロンプトでは日本語が文字化けしてしまって読めない。おまけにどういうわけか、コマンドプロンプトのフォントが変更されてしまうという、謎の現象に見舞われた。
これでは使えないので、じゃあ、Go で整形するだけのツールを書いてみようか、と思って書いたら意外にも簡単だった、というのが今日の話。
サンプルの JSON はこんなの。Web で拾ったサンプルだけど、どこのページだか忘れてしまった。
{ "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "pref": "富山県", "city1": "下新川郡", "city2": "朝日町" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 136.111111, 36.111111 ], [ 136.222222, 36.222222 ] ] ] } }, { "type": "Feature", "properties": { "pref": "富山県", "city1": "氷見市", "city2": "" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 136.333333, 36.333333 ], [ 136.444444, 36.444444 ] ] ] } }, { "type": "Feature", "properties": { "pref": "富山県", "city1": "高岡市", "city2": "" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 136.555555, 36.555555 ], [ 138.666666, 36.666666 ] ] ] } } ]}
全部が1行に詰め込まれてる上に、日本語が混じっている。
で、書いたツールがこれ。pj という名前にした(ファイル名は main.go だけど)。整形には encoding/json
パッケージの Indent
関数を使っている。
package main import ( "fmt" "encoding/json" "bytes" "os" "io/ioutil" "flag" ) const ( progVersion = "v0.1.0" ) func prettyJson(src []byte) string { buf := make([]byte, 0) dst := bytes.NewBuffer(buf) err := json.Indent(dst, src, "", " ") if err != nil { fmt.Fprintln(os.Stderr, err) os.Exit(1) } return dst.String() } func main() { flag.Usage = func() { fmt.Fprintf(os.Stderr, `Usage: %s [options] Options: `, os.Args[0]) flag.PrintDefaults() } opt_version := flag.Bool("version", false, "Show version.") flag.Parse() if *opt_version { fmt.Println(progVersion) os.Exit(0) } infile := flag.Args()[0] src, err := ioutil.ReadFile(infile) if err != nil { fmt.Fprintln(os.Stderr, err) os.Exit(1) } pretty := prettyJson(src) fmt.Println(pretty) }
試してみよう。
^o^ > go build ^o^ > pj sample.json { "type": "FeatureCollection", "features": [ { "type": "Feature", "properties": { "pref": "富山県", "city1": "下新川郡", "city2": "朝日町" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 136.111111, 36.111111 ], [ 136.222222, 36.222222 ] ] ] } }, { "type": "Feature", "properties": { "pref": "富山県", "city1": "氷見市", "city2": "" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 136.333333, 36.333333 ], [ 136.444444, 36.444444 ] ] ] } }, { "type": "Feature", "properties": { "pref": "富山県", "city1": "高岡市", "city2": "" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 136.555555, 36.555555 ], [ 138.666666, 36.666666 ] ] ] } } ] }
何のデータなんだかよくわからないけど、うまくいった。もとのエンコーディングは UTF-8 だけど、Windows のコマンドプロンプトに出力しても文字化けしない。もちろんファイルに書き出せばちゃんと UTF-8 で保存される。
うまくいったので GitHub にも公開しておいた。
cf. https://github.com/takatoh/pj
インストールするにはこうすればいい:
^o^ > go get github.com/takatoh/pj
定型のデータを読み込んで、型変換をしてくれる関数があると便利だ。
fmt
パッケージの Scan
と Fscan
は空白文字で区切られたテキストデータを読み込み、可変長引数で渡されたデータ形式の変数(のポインタ)に変換して格納してくれる。Scan
は標準入力から、Fscan
は io.Reader
から読み込む。
まずは Scan
の例。
package main import ( "fmt" "os" "io" ) func main() { sumi := 0 sumf := 0.0 suma := make([]string, 0) for { var n int var m float64 var s string i, err := fmt.Scan(&n, &m, &s) if i == 3 { sumi += n sumf += m suma = append(suma, s) } else if i == 0 && err == io.EOF { break } else { fmt.Fprintln(os.Stderr, err) os.Exit(1) } } fmt.Println(sumi, sumf, suma) }
^o^ > go run scan.go 1 1.5 1 2 2.3 2 3 3.8 3 6 7.6 [1 2 3]
go run scan.go
コマンドに続く3行が入力で、1行ごとに整数、実数、文字列に変換される。ctrl + c
で入力を終了すると、結果が表示される。最後の行が結果だ。
次は Fscan
の例。io.Reader
から(つまりファイルから)読み込む。
package main import ( "fmt" "os" "io" ) func main() { sumi := 0 sumf := 0.0 suma := make([]string, 0) filename := os.Args[1] file, err := os.Open(filename) if err != nil { fmt.Fprintln(os.Stderr, err) os.Exit(1) } for { var n int var m float64 var s string i, err := fmt.Fscan(file, &n, &m, &s) if i == 3 { sumi += n sumf += m suma = append(suma, s) } else if i == 0 && err == io.EOF { break } else { fmt.Fprintln(os.Stderr, err) os.Exit(1) } } file.Close() fmt.Println(sumi, sumf, suma) }
^o^ > cat fscan_sample.txt 10 1.234 foo 20 5.678 bar 30 9.876 baz ^o^ > go run fscan.go fscan_sample.txt 60 16.788 [foo bar baz]
Scanf
と Fscanf
は書式付き入力だ。引数で指定された書式で入力を解釈して読み込んでくれる。Scanf
は標準入力から、Fscanf
は io.Reader
(つまりファイル)から読み込む。
Scanf
の例。
package main import ( "fmt" "os" ) func main() { sumi := 0 sumf := 0.0 suma := make([]string, 0) for { var n int var m float64 var s string i, err := fmt.Scanf("%d,%f,%q\n", &n, &m, &s) if i == 3 { sumi += n sumf += m suma = append(suma, s) } else if i == 0 { break } else { fmt.Fprintln(os.Stderr, err) os.Exit(1) } } fmt.Println(sumi, sumf, suma) }
この例では整数、実数、クォートされた文字列をカンマ区切りしたものを入力として期待している。
^o^ > go run scanf.go 10,1.234,"foo" 20,5.678,"bar" 30,9.876,"baz" 60 16.788 [foo bar baz]
最後に Fscanf
。これはファイルから読み込むことのほかは Scanf
と同じだ。
package main import ( "fmt" "os" ) func main() { sumi := 0 sumf := 0.0 suma := make([]string, 0) filename := os.Args[1] file, err := os.Open(filename) if err != nil { fmt.Fprintln(os.Stderr, err) os.Exit(1) } for { var n int var m float64 var s string i, err := fmt.Fscanf(file, "%d,%f,%q\n", &n, &m, &s) if i == 3 { sumi += n sumf += m suma = append(suma, s) } else if i == 0 { break } else { fmt.Fprintln(os.Stderr, err) os.Exit(1) } } file.Close() fmt.Println(sumi, sumf, suma) }
^o^ > cat fscanf_sample.txt 10,1.234,"foo" 20,5.678,"bar" 30,9.876,"baz" ^o^ > go run fscanf.go fscanf_sample.txt 60 16.788 [foo bar baz]