数値計算するbcコマンドが便利

こんにちは suganoo です。

シェルスクリプトを書いてたりLinuxのコマンドをたたいてると単純な計算をしたいときがあります。

単純な足し算やfor文内のインクリメントとか。

そんなんであれば $((...)) でどうにかなります。

例えば足し算なら

CNT=$(( CNT + 1 ))

とか

forループとかだとこんな感じ

CNT=0
for MUDA in {1..10}
do
  CNT=$(( CNT + 1 ))
done
echo ${CNT}

ですが、例えば wc -l でいくつかのファイルの行数を取って来てて合算したいとか、du -sm でファイルサイズ取って来てサイズを合算したいときとか、ちょちょっと計算したいときがあります。

それもわざわざ計算機アプリ起動したくないし、Chromeで打ち込むとかしたくない。

それをコマンドでできないか?


そんな時にbcコマンドが便利です。

使い方は簡単で echo に計算式を出力してやってパイプでbcに流せばいいです。

$ echo "1 + 3" | bc
4

小数点はscaleで指定すればOK

$ echo "scale=5; 7 / 6" | bc
1.16666

いやーこの小数点指定をお手軽にできるのはシェルスクリプト書くときに便利なんですよ!

他にもパラメーターに -l を付ければ、sin, cos, arctan, log, exp(自然対数)、sqrtも使えます。

関数 意味
s() sin
c() cos
a() arctan
l() log
e() 自然対数のe
sqrt() 平方根

man bcで調べると色々出てきます。

$ echo "s(1)" | bc -l
.84147098480789650665

他にも変数も使えます。

$ echo "a=3 ;b=2 ;a+b" | bc
5

bcコマンドをそのまま実行すればインテラクティブモードで使えます。

$ bc
bc 1.06.95
Copyright 1991-1994, 1997, 1998, 2000, 2004, 2006 Free Software Foundation, Inc.
This is free software with ABSOLUTELY NO WARRANTY.
For details type `warranty'.
1 + 2
3
quit

exprコマンドより使いやすい感じしますねー

シェルスクリプトで配列を扱う

こんにちは suganoo です。

シェルスクリプトを書いていて配列ってどうやるんだっけ?っと

たびたび調べることが多いので記事に書いておこうと思います。

コマンドの結果を配列にする

まずコマンドの結果を配列に入れる方法

FILE_LIST=(`ls`)

コマンドを``で囲んで、かっこでくくればいいですね。
はい、そんだけ

配列のループ

配列のループは配列番号の代わりに@にすればいいです

for FILE in ${FILE_LIST[@]}
do
....
done

サンプルコード

サンプルコードを書いてみます
カレントディレクトリ配下にあるファイルをリストアップする処理です。

#!/bin/bash

FILE_LIST=(`ls`)

for FILE in ${FILE_LIST[@]}
do
  echo "${FILE}"
done


簡単ですねー


FILE_LIST=(`ls`) のようにコマンドの結果を配列型として変換できるのをよく忘れていました。

これでもう忘れてないはず

速度制限するパッケージ golang.org/x/time/rateについてQiitaに書いてみた

こんにちは suganoo です。

ここ最近3月4月とブログをあまり更新してませんでした。

というのも「Go言語による並行処理」が勉強になったと以前投稿したのですが、
読んでるだけじゃだめだ!やっぱ写経しないと!っと思ってサンプルコードを写経してたわけで更新してませんでした。

以前の記事はこれ
Go言語 channel の使い方を勘違いしてたこと - S氏はたまにblogを更新してます
「Go言語による並行処理」を読んでみたけどめちゃ勉強になる本だった - S氏はたまにblogを更新してます


いやーほんと写経やってみてわかるんですけどね

写経はかなり力がつく!理解が深まる!

っと思います。

細かいところの書き方とかが、あーこういう風に書いてるのねとわかるので、ほんと勉強になるなと思ったソースは写経してみることをオススメします。

写経してみた成果物はこちら
github.com

写経してみてわかったんだけど、本家のサンプルコードでも一部作りかけで動かないコードがあったのでPR作ってみました。更新してないので、たぶん取り込まれないと思う。
github.com


んで、写経してて後半の方に入ると流量制限できるサンプルがありました。
golang.org/x/time/rate というパッケージです。

ざっくりいうと1秒間に10回の速さで実行をさせたい、とか言った時に使えるパッケージです。

このrateパッケージがなかなか面白かったので動作を調べてQiitaに投稿してみました。

qiita.com


いろいろ本を読んで興味あるところに手を出してみると面白いものがいろいろ発見できますね。

またたくさんの本を読むより、一冊の本を写経なりしてしっかり読み込む方が血肉になって力がつくことがよくわかりました。

うん、やっぱ手を動かすのが大事ですね~

Go言語による並行処理

Go言語による並行処理

  • 作者: Katherine Cox-Buday,山口能迪
  • 出版社/メーカー: オライリージャパン
  • 発売日: 2018/10/26
  • メディア: 単行本(ソフトカバー)
  • この商品を含むブログを見る

Go言語でのBasic認証とDigest認証のヒント

f:id:suganoo:20190425131003p:plain
久しぶりの投稿でございます。

たまたま社内のjiraにAPIでissueを作ろうと思ったのですが、
その前のプロキシーサーバーがDigest認証になってました。

はてと困りました。

結局はプログラムで解決するのは難しそうなので、直接curlでどうにかしようと思いましたが
それにいたるまでプログラムで解決する方法がGitHubで見つけちゃったりと
資料がみつかったので書いておきます。

概要の理解
madalinazaharia.com

プロキシのBasic認証はこれでいけそう
qiita.com

Goでhttpリクエストする
qiita.com

GoでDigest認証する
このソースコードはほんと参考になりました!
時間があればこれをもとにして書いてみたかった...。
github.com

ヘッダー情報の確認
unarist.hatenablog.com


なるほど勉強になりますね。

Go言語 channel の使い方を勘違いしてたこと

「Go言語による並行処理」を写経してるんですが、チャネルのところで理解が甘かったところがありました。

やってみて、ああそうだったのか!?と気づきました。

わかってなかったことは下記の2つ

  • チャネルってgoroutineで使うもの。
  • そのgoroutineの中でcloseしても大丈夫(むしろこうするべき)

いやーいい加減に理解してはダメですね。

チャネルを閉じて値を取得してみる

サンプルコードを見てみましょう。「Go言語による並行処理」から抜粋してみます。

サンプル01
https://play.golang.org/p/AjTlu6R4K-p

package main

import "fmt"

func main() {
        intStream := make(chan int)
        close(intStream)
        integer, ok := <- intStream
        fmt.Printf("(%v): %v", ok, integer)
}

結果は下記になります

(false): 0

なにも値をいれてないチャネルから値を取り出しても大丈夫だよ、ということです。

main() でチャネルに値を入れてみる

ではここで intStream チャネルになにか値を入れてみましょう。
そのために close(intStream) もdefer にします。

サンプル02
https://play.golang.org/p/3EemcCFsELK

package main

import "fmt"

func main() {
	intStream := make(chan int)
	defer close(intStream)
	intStream <- 3
	integer, ok := <- intStream
	fmt.Printf("(%v): %v", ok, integer)
}

結果は下記になります。

fatal error: all goroutines are asleep - deadlock!

goroutine 1 [chan send]:
main.main()
	/tmp/sandbox674957142/main.go:8 +0x80

え!?と思いましたがゴルーチン以外で値を入れるとデッドロックになるんですね!

ゴルーチンから値を入れてみる

ではここで intStream チャネルをgoroutine から値を入れてみます。

サンプル03
https://play.golang.org/p/ZZRzjnqnz2v

package main

import "fmt"

func main() {
	intStream := make(chan int)
	defer close(intStream)
	go func() { intStream <- 3 }()
	integer, ok := <- intStream
	fmt.Printf("(%v): %v", ok, integer)
}

結果です

(true): 3

うまくいきましたね。

ゴルーチンでチャネルを閉じてみる

ではここで defer close(intStream) を goroutine に移してみましょう。

予想として、goroutine の中でチャネルを閉じてしまったら受け取るときにエラーなるんじゃないか!?っと思ってました。
だってチャネルを閉じてるんだもん。

サンプル04
https://play.golang.org/p/BfrB7K1g8QR

package main

import "fmt"

func main() {
	intStream := make(chan int)
	go func() {
		defer close(intStream)
		intStream <- 3
	}()
	integer, ok := <- intStream
	fmt.Printf("(%v): %v", ok, integer)
}

結果です

(true): 3

エラーにならず正常な結果ですね!

待受け側 for ... range チャンネルは事前にチャンネルのcloseが必要

あともう一つチャンネルの close で気になった性質。

チャンネルに値を入れた後に、そのチャンネルから値を取得する場合って for ... range で値を取得する場合がありますよね。
下記のサンプルを見てみてください。

サンプル05
https://play.golang.org/p/aA0t3EVTCxn

package main

import "fmt"

func main() {
	// 値が入ったチャンネルを返す関数
	f := func() chan int{
		// チャンネル作って
		intStream := make(chan int)
		
		// ゴルーチンの中で値入れて
		go func() {
			defer close(intStream)
			intStream <- 3
		}()
		
		// チャンネルを返す
		return intStream
	}
	
	ch := f()
	// range で値を取得
	for integer := range ch {
		fmt.Printf("%v ", integer)
	}
}
3

結果はちゃんと取得できます。

この時ゴルーチンの中のチャンネルcloseしなかったらどうなるのかな?と思いました。

サンプル06
https://play.golang.org/p/PYvnH83M8Oe

package main

import "fmt"

func main() {
	// 値が入ったチャンネルを返す関数
	f := func() chan int{
		// チャンネル作って
		intStream := make(chan int)
		
		// ゴルーチンの中で値入れて
		go func() {
			//defer close(intStream)    <-- これな!!
			intStream <- 3
		}()
		
		// チャンネルを返す
		return intStream
	}
	
	ch := f()
	// range で値を取得
	for integer := range ch {
		fmt.Printf("%v ", integer)
	}
}

結果はこれでした

3 fatal error: all goroutines are asleep - deadlock!

goroutine 1 [chan receive]:
main.main()
	/tmp/sandbox416365219/main.go:23 +0x100

デッドロックエラーになるんですね。
考えてみればそりゃそうですよね。for で値取得をずーっと待つことになるんですから終わりきらないのでdeadlock。当たり前だ。

ではこの値取得を <- で取得したらどうなるのか?

サンプル07
https://play.golang.org/p/d9AjTBsc3Q6

package main

import "fmt"

func main() {
	// 値が入ったチャンネルを返す関数
	f := func() chan int{
		// チャンネル作って
		intStream := make(chan int)
		
		// ゴルーチンの中で値入れて
		go func() {
			//defer close(intStream)    <-- これな!!
			intStream <- 3
		}()
		
		// チャンネルを返す
		return intStream
	}
	
	ch := f()
	// <- で値を取得
	num := <- ch
	fmt.Printf("%v ", num)
}
3

値はちゃんと取れますね。<- で値を取得するだけであればチャンネルをcloseしなくても値取得できるんですね。
チャンネルをちゃんと閉じないのはあまりよろしくないですが。。。

チャネル所有するゴルーチンはチャネルを閉じる責任がある

「Go言語による並行処理」のP77では、ざっくりですが、チャネル所有するゴルーチンはチャネルを閉じる責任があるそうです

そうすることでチャネル所有者がチャネルを閉じるので、閉じたチャネルに書き込んでしまったり、2度チャネルを閉じてしまったり、といったpanicを引き起こすことがなくなります。

なるほどーチャネルってどこでも使えるのかと思ってましたが、そうでもないんですね。
勉強になりました!

Go言語による並行処理

Go言語による並行処理

  • 作者: Katherine Cox-Buday,山口能迪
  • 出版社/メーカー: オライリージャパン
  • 発売日: 2018/10/26
  • メディア: 単行本(ソフトカバー)
  • この商品を含むブログを見る

UdemyのgRPCコースやってた

久しぶりの更新になりました。

2月半ばにこんな企画を見つけたのでUdemyやってました。
zine.qiita.com

Udemyって年末年始に90%オフセールしたりして安くなるんですが、買った後なにもやらずにそのままにしちゃうんですよね。

いわゆる積んdemy。

それをこの機会にやってみました。

やってみたのはこれ
gRPC [Golang] Master Class: Build Modern API & Microservices

せこせこ写経してQiitaに投稿してみました。
qiita.com

時間に追われてたとはいえかなり勉強になりました。


それにしてもUdemyってマイナーな技術でもコースがあったりするからいいです。

仮想通貨が出たときもいち早くEthereumのコースが出てたしな。

セールもたびたびやってるのでちょこちょこ探してみるのおすすめです。
Udemy

blog.suganoo.net

とがったことができる人より基本のレベルが高い人 しっかりGo言語を書くために ~"golang.tokyo#21「Goエンジニアの採用」レポート""を読んで~

golang.tokyo#21「Goエンジニアの採用」というイベントがあったそうです。
行ってみたかった。
golangtokyo.connpass.com

そのイベントでレポートをアップした方がいましたが、内容がとても良かった。
その中で身を引き締まる内容がありました。
mom0tomo.github.io

「lsしたときに受けた印象が変わることはほとんどない」

言わんとすることは、評価者の立場になってくれよってことだと思います。

他にも、go fmt や golint などソースをすぐに整理するコマンドなんて手軽にできるはずなんだからやっておくべきだなと再確認しました。

下記のリンクはちゃんと読むべきだなと思いました。

qiita.com

golang.org

しっかりGo言語勉強しようっと

追記
これもおまけ
ukai-go-talks.appspot.com

【Python】TypeError: string indices must be integers, not str

こんにちは suganoo です。

Pythonでまた似たようなエラーが出たので備忘として書いておきます。

このエラーなんだっけなと基本的なものなのにたまにど忘れしてしまいました。

TypeError: string indices must be integers, not str

stringのインデックス型でintで指定するところstringで指定してるよ。
っといった意味です。

わかりやすいサンプルコードを書いてみます。

str1 = "Hello World!"

print str1
print str1[:2]
print str1["hoge"]
Hello World!
He
Traceback (most recent call last):
  File "stringindices.py", line 6, in <module>
    print str1["hoge"]
TypeError: string indices must be integers, not str

インデックスは数値でアクセスしなければならないのに、文字列を指定してみるとこんなエラーがでます。

参考にしてみてください。
blog.suganoo.net

【Go】Go言語でSQLを実行してみる

f:id:suganoo:20190125170942p:plain
こんにちは suganoo です。

Go言語からのDB操作を復習したいと思います。「Goプログラミング実践入門」でDBの操作が出ていたので、おさらいをしておきたいなと思ってました。
今回はORMは使いません。それはそれでいつかまた調べて記事にしてみようと思います。

DB設定

PostgreSQLのインストール

本記事ではPostgreSQLを使っています。インストール作業は他のサイトなどを見て設定してみてください。
www.dbonline.jp

DB, テーブル設定

プログラムの中でCREATE DATABASE/TABLE からやってみようかなと思いましたが、考えてみてもそんな場面はほとんどないので事前にDB設定とCREATE TABLEはしておきます。

-- データベースは postgresを使います
create table members (
  id          serial primary key,
  first_name  varchar(255),
  last_name   varchar(255),
  email       varchar(255),
  accessprev  boolean
);

INSERT INTO members VALUES (1, 'minoru', 'tanaka', 'tanaka@gmail.com', TRUE);
INSERT INTO members VALUES (2, 'tadashi', 'sato', 'sato@gmail.com', FALSE);
INSERT INTO members VALUES (3, 'sachiko', 'suzuki', 'suzuki@yahoo.co.jp', FALSE);

テーブルの中身を確認するとこうなってると思います。

id first_name last_name email accessprev
1 minoru tanaka tanaka@gmail.com t
2 tadashi sato sato@gmail.com f
3 sachiko suzuki suzuki@yahoo.co.jp f

DB操作

import設定

今回はPostgreSQLを使うので、それ用のdriverをインポートします。

import (
        "database/sql"
        _ "github.com/lib/pq"
)

データ取得用 Member構造体

データ取得用にMember構造体を定義しておきます。

type Member struct {
        Id         int
        FirstName  string
        LastName   string
        Email      string
        AccessPrev bool
}

サンプルコード全体

先に以降で説明するサンプルコードを掲載しておきます。

Go言語でDB操作

Query()

単純にクエリ実行したい場合に使えます。
sql - The Go Programming Language

func (db *DB) Query(query string, args ...interface{}) (*Rows, error)

サンプルコード

rows, err := Db.Query("SELECT * FROM members ORDER BY id")
if err != nil {
        return
}
for rows.Next() {
        m := Member{}
        rows.Scan(&m.Id, &m.FirstName, &m.LastName, &m.Email, &m.AccessPrev)
        fmt.Println(m)
}

Query()はRows型を返します。
sql - The Go Programming Language

SQLの結果が複数行を想定しているからです。
そんでそのメソッドのNext()で行があるか判断して、Scan()で値を取得しています。

Query() プレイスホルダー

このQuery()関数はプレイスホルダーも使えます。SQL分に$1, $2, $3 といった書き方をして、引数で実際の値を設定するというものです。

rows, err = Db.Query("SELECT * FROM members WHERE accessprev = $1 ORDER BY id", "false")

値はたいてい別々に取得するものなのでSQL文に入れる必要がないので見やすくなりますね。SQL文の中では$1, $2, $3...の順番はありません、引数に指定する実際の値の順番が$1, $2, $3...になるので順番を間違えないようにしましょう。

QueryRow()

そんで次にQueryRow()です。
https://golang.org/pkg/database/sql/#DB.QueryRow

func (db *DB) QueryRow(query string, args ...interface{}) *Row

QueryRow()はRow型を返します。一行のSQL結果が返るを想定してるのでScan()しかないですね。

サンプルコードです

m := Member{}
err = Db.QueryRow("SELECT * FROM members WHERE accessprev = $1 ORDER BY id", "true").Scan(&m.Id, &m.FirstName, &m.LastName, &m.Email, &m.AccessPrev)
if err != nil {
        return
}
fmt.Println(m)

Prepare()

SQL文だけでStmtオブジェクトを作り、クエリを実行するメソッドと分けることができます。
sql - The Go Programming Language

func (db *DB) Prepare(query string) (*Stmt, error)

サンプルコードです

statement := "SELECT * FROM members WHERE accessprev = $1 ORDER BY id"
stmt, err := Db.Prepare(statement)
if err != nil {
        return
}
defer stmt.Close()

rows, err = stmt.Query("false")
for rows.Next() {
        m := Member{}
        rows.Scan(&m.Id, &m.FirstName, &m.LastName, &m.Email, &m.AccessPrev)
        fmt.Println(m)
}

抜粋ですが実行メソッドが分けられているのがわかるかと思います。

statement := "SELECT * FROM members WHERE accessprev = $1 ORDER BY id"
stmt, err := Db.Prepare(statement)
defer stmt.Close()  // Close()が必要!!
rows, err = stmt.Query("false")

Exec()

単純にクエリを実行し、結果行を戻さないメソッドです。たいていは delete, insert で使うと思います。
sql - The Go Programming Language

func (db *DB) Exec(query string, args ...interface{}) (Result, error)

返すResult型を見てもどこの行に入ったかとかを返すようですね。データベース依存みたいです。
sql - The Go Programming Language
サンプルコードです

_, err = Db.Exec("INSERT INTO members VALUES ($1, $2, $3, $4, $5)", 4, "takashi", "yamamoto", "yamamoto@yahoo.co.jp", "FALSE")

Contextも渡せる

ここまで基本的なメソッドを見てきましたが、ドキュメントを見てみるとcontex.Contextを渡せるメソッドも用意されているんですね。
例えば QueryContext()とか
sql - The Go Programming Language

func (db *DB) QueryContext(ctx context.Context, query string, args ...interface{}) (*Rows, error)

確かにSQLでクエリに時間がかかることもありますから、タイムアウト時間も設定したいものです。けっこう便利ですな。

context.Context()の使い方については下記の記事が参考になります。
blog.suganoo.net

Named()はまだSQliteだけみたい

そんでまたドキュメントを見てみるとNamed()メソッドなるものも見つかりました。
sql - The Go Programming Language

func Named(name string, value interface{}) NamedArg

Exampleを見てみると、@で任意のプレイスホルダーを指定できるように見えます。

db.ExecContext(ctx, `
    delete from Invoice
    where
        TimeCreated < @end
        and TimeCreated >= @start;`,
    sql.Named("start", startTime),
    sql.Named("end", endTime),
)

なんだsql.Named() 優秀やーん!!
.....っと思ったのですが、実はこれまだ使えるDBは多くないそうです
mattn.kaoriya.net
Named()を使えるのはSQLiteだけみたいですね。

私はこれに気づかずPostgreSQLで試行錯誤してみましたが、ぜんぜん使えず半日以上を無駄にしました。

issueにはあるみたいですが、まだ使えないようですね。
github.com
github.com

database/sql ドキュメントのExample

あとdatabase/sqlのドキュメントを調べて気づきましたが、Exampleがことごとくすべてエラーになってます。これいいんですかねえ.....。
golang.org

まあDBがないとできないようなExampleだから、仕方ないといえばそうなのかもだけど。。。

最後

他にもいろいろ関数はありましてColumnType型やカラム一覧が取得できる関数などもありますが、上記の基本的な使い方がわかればあとは何とかなるかと思います。

次回はgormでもやってみたいと思います。

<こんな記事もあります>
blog.suganoo.net
blog.suganoo.net

達人に学ぶDB設計 徹底指南書 初級者で終わりたくないあなたへ

達人に学ぶDB設計 徹底指南書 初級者で終わりたくないあなたへ

Go言語での定期実行とタイムアウト(Software Design 2019年2月号の復習)

f:id:suganoo:20190123001521p:plain
こんにちは suganoo です。

今月号のSoftware Design (2019年2月号)に「”速い”コードの書き方」という特集がありました。Go言語についても書かれていたので、おお!っとさっそく読み込んでしまいました。

ソフトウェアデザイン 2019年2月号

ソフトウェアデザイン 2019年2月号

  • 作者: なぎせゆうき,成瀬ゆい,石本敦夫,mattn,片山善夫,藤崎正範,清水勲,岩永翔,柘植翔太,吉川拓哉,馬場俊彰,竹端尚人,吉田英二,安藤幸央,結城浩,武内覚,宮原徹,平林純,くつなりょうすけ,上田拓也,職業「戸倉彩」,上田隆一,田代勝也,eban,山田泰宏,中村壮一,速水祐,中谷克紀,小飼弾,すずきひろのぶ,青田直大,やまねひでき,中島雅弘,あわしろいくや,榎真治,細矢研人,後藤大地,杉山貴章,Software Design編集部
  • 出版社/メーカー: 技術評論社
  • 発売日: 2019/01/18
  • メディア: 雑誌
  • この商品を含むブログを見る

内容としてはGo言語の特徴であるgoroutineを使った非同期処理やタイムアウトがまとめられています。goroutineを使ってchanで非同期処理ってけっこうやってみないと理解できなかったりするので、あのページ数にまとめていたのはけっこう大変だったのではと推測してしまいます。
たまたま自分はGo言語で非同期処理のツールを作ってたので復習として読めてラッキーでした。

またタイミングが良かったことに、WebSokcketでチャットアプリを作ってみたいなと思っていまして、Go言語でWebSocketっというとgorilla/websocketがよく使われているのでそれをよく読んでいました。

https://github.com/gorilla/websocket

そこのexamples/echoでお勉強していたのですが、定期実行を下記のようにtimeパッケージのticker.Cでやっていたんですね。最初はこれが何やってるのかわかりませんでした。

	ticker := time.NewTicker(time.Second)
	defer ticker.Stop()

	for {
		select {
		case <-done:
			return
		case t := <-ticker.C:
			err := c.WriteMessage(websocket.TextMessage, []byte(t.String()))
..........

そこでSoftware Designにも書かれていたことだし自分の復習もかねて定期実行とタイムアウトを調べてみました。
※以降のコードはSoftware Design (2019年2月号)も参考にしています。

ctrl + c でプログラムを止める

その前にプログラムを実行中にプログラムを止めたい場合があります。
そのやり方をざっくり書いておきます。

import "os"
.......
        sc := make(chan os.Signal, 1)  // シグナル用のチャンネル作って
        signal.Notify(sc, os.Interrupt)  // シグナルを登録する
loop:
        for {
                select {
                case <- sc:  // <---- ctrl + c を押すとここでキャッチされる。
                        fmt.Println("interrupt")
                        break loop
                }
        }

定期実行する

いくつかやり方はあるそうですがここではtime.After を使う方法と、NewTickerを使う方法、time.Tickを使う方法を紹介します。

time.After()

https://golang.org/pkg/time/#After

func After(d Duration) <-chan Time

例えば2秒ごとに何かを実行したいなら、こんな感じに書けます。
Software Design (2019年2月号) ではこのように書いてますね。

case <-time.After(2 * time.Second):  

サンプルコードです

package main

import (
        "fmt"
        "os"
        "os/signal"
        "time"
)

func doSomething() {
        fmt.Println(time.Now())
}

func main() {
        sc := make(chan os.Signal, 1)
        signal.Notify(sc, os.Interrupt)
loop:
        for {
                select {
                case <- sc:
                        fmt.Println("interrupt")
                        break loop
                        //os.Exit(0)
                //case t := <-time.After(2 * time.Second):
                //      fmt.Println(t)
                case <-time.After(2 * time.Second):
                        doSomething()
                }
        }
}

実行結果です。以降実行結果はどれも同じなので省略します。

2019-01-22 13:48:58.5850586 +0900 JST m=+2.025493201
2019-01-22 13:49:00.5845793 +0900 JST m=+4.025013901
2019-01-22 13:49:02.5862735 +0900 JST m=+6.026708101
interrupt     // ctrl + c を押す

またループの抜け方ですが、Software Design (2019年2月号)に書いてありましたが上記のようにbreak loopで抜けられます。Go言語ってラベルでループを抜けるとかダメじゃないんですよね。もしくは os.Exit()でプログラムを終わらすこともできます。

またちなみにですが、下記のようにチャネルの結果を取得することも可能で、表示させてみるとtime型のオブジェクトが取得できます。以降の例も同様です。

case t := <--time.After(...):
        fmt.Println(t)  // --> ex. "2019-01-22 13:49:00.5845793 +0900 JST m=+4.025013901"

time.NewTicker()

time.NewTickerを使うとこんな感じです。
https://golang.org/pkg/time/#Ticker

func NewTicker(d Duration) *Ticker

type Ticker struct {
        C <-chan Time // The channel on which the ticks are delivered.
        // contains filtered or unexported fields
}
.....
tk := time.NewTicker(2 * time.Second)
defer tk.Stop()
.....
case <-tk.C:  
  ...

サンプルコードです

package main

import (
        "fmt"
        "os"
        "os/signal"
        "time"
)

func doSomething() {
        fmt.Println(time.Now())
}

func main() {
        sc := make(chan os.Signal, 1)
        signal.Notify(sc, os.Interrupt)

        tk := time.NewTicker(2 * time.Second)
        defer tk.Stop()
loop:
        for {
                select {
                case <- sc:
                        fmt.Println("interrupt")
                        break loop
                case <-tk.C:
                        doSomething()
                }
        }
}

time.Tick()

https://golang.org/pkg/time/#Tick

func Tick(d Duration) <-chan Time

time.Tick()を使うとこんな感じです。

for _ = range time.Tick(2 * time.Second) {
        doSomething()
}

サンプルコードです。

package main

import (
        "fmt"
        "os"
        "os/signal"
        "time"
)

func doSomething() {
        fmt.Println(time.Now())
}

func main() {
        sc := make(chan os.Signal, 1)
        signal.Notify(sc, os.Interrupt)

        go func() {
                for _ = range time.Tick(2 * time.Second) {
                        doSomething()
                }
        }()

loop:
        for {
                select {
                case <- sc:
                        fmt.Println("interrupt")
                        break loop
                }
        }
}

どれも <-chan 型を返してきてますね。

context.WithCancel, WithTimeout, WithDeadline

WithCancel, WithTimeout, WithDeadlineを使うと別プロセスから特定の関数をキャンセルすることができます。
ちょっとここでWithDeadlineについては特定の時間が来たらキャンセルになる機能でして、時間を指定すればいいだけなので説明は省きます。WithDeadlineの引数に特定の時間を入れればいいです。
https://golang.org/pkg/context/#WithDeadline

WithCancel

WithCancelは(というかWithTimeoutもWithDeadlineもですが)第二パラメーターにCancelFuncを返します。そのCancelFuncを実行するとContextを受け継いだ関数がキャンセルされます。
https://golang.org/pkg/context/#WithCancel

func WithCancel(parent Context) (ctx Context, cancel CancelFunc)

サンプルコードです

package main

import (
        "context"
        "fmt"
        "time"
)

func doSomething(ctx context.Context) {
        fmt.Println("5s sleep start from doSomething.")  // (4)
        time.Sleep(5 * time.Second)    // (5)
        fmt.Println("End doSomething.")   // (2)で2秒待って(3)でキャンセルされるので表示されない
}

func main() {
        ctx := context.Background()
        ctx, cancel := context.WithCancel(ctx)

        go doSomething(ctx)

        fmt.Println("2s sleep start")  // (1)
        time.Sleep(2 * time.Second) // (2)

        cancel()     // (3)

        select {
        case <-ctx.Done():    // (6)
                fmt.Println("done:", ctx.Err())
        }
}

Playgroundで動作確認してみる

実行結果

2s sleep start   // (1)が実行
5s sleep start from doSomething.   // (4)が実行
// (2)の2秒待機
done: context canceled  // 5秒待たず(3)でキャンセルされdoSomething()が終わり(6)に来る

WithCancelを使わない場合はわざわざキャンセル用のチャンネルを作ってdoSomethingに持たせ、forでループさせて待機させる必要があります。その点WithCancelのおかげでプログラムが簡素になりました。

キャンセルの連鎖

WithCancelを連鎖させることもできます。連鎖というとぷよぷよを思い出してしまいますね。ですがそんなイメージです。

サンプルコードを見てみましょう。

package main

import (
        "context"
        "fmt"
        "time"
)

func doSomethingParent(ctx context.Context) {
        fmt.Println("5s sleep start from doSomethingParent.")
        time.Sleep(5 * time.Second)
        fmt.Println("End doSomethingParent.")
}

func doSomethingChild(ctx context.Context) {
        fmt.Println("3s sleep start from doSomethingChild.")
        time.Sleep(3 * time.Second)
        fmt.Println("End doSomethingChild.")
}

func main() {
        ctx := context.Background()
        pCtx, pCancel := context.WithCancel(ctx)
        cCtx, cCancel := context.WithCancel(pCtx)
        defer cCancel()
        //defer pCancel()

        go doSomethingParent(pCtx)
        go doSomethingChild(cCtx)

        fmt.Println("2s sleep start")
        time.Sleep(2 * time.Second)

        pCancel()  // doSomethingParent 関数が終わりきる前に親関数をキャンセルする。
        //cCancel()

        select {
        case <-pCtx.Done():
                fmt.Println("done Parent Context :", pCtx.Err())
                fmt.Println("done Child  Context :", cCtx.Err())
        case <-cCtx.Done():
                fmt.Println("done Parent Context :", pCtx.Err())
                fmt.Println("done Child  Context :", cCtx.Err())
        }
}

Playgroundで動作確認してみる

実行結果

2s sleep start
3s sleep start from doSomethingChild.
5s sleep start from doSomethingParent.
done Parent Context : context canceled // <--- (pCancel)親関数がキャンセルされたから
done Child  Context : context canceled  // <--- 子関数もキャンセルされてるよ!!

そこで今度は子関数を先にキャンセルしてみましょう。main()だけ記載します。

func main() {
        ctx := context.Background()
        pCtx, pCancel := context.WithCancel(ctx)
        cCtx, cCancel := context.WithCancel(pCtx)
        //defer cCancel()
        defer pCancel()

        go doSomethingParent(pCtx)
        go doSomethingChild(cCtx)

        fmt.Println("2s sleep start")
        time.Sleep(2 * time.Second)

        //pCancel()
        cCancel()  // <--- 子関数をキャンセルする。

        select {
        case <-pCtx.Done():
                fmt.Println("done Parent Context :", pCtx.Err())
                fmt.Println("done Child  Context :", cCtx.Err())
        case <-cCtx.Done():   // <-- こっちが実行されるよ!
                fmt.Println("done Parent Context :", pCtx.Err())
                fmt.Println("done Child  Context :", cCtx.Err())
        }
}

Playgroundで動作確認してみる

実行結果

2s sleep start
3s sleep start from doSomethingChild.
5s sleep start from doSomethingParent.
done Parent Context : <nil>  // <---  親関数のcontextはキャンセルされてない!
done Child  Context : context canceled

想像通り子関数だけキャンセルされますね。
ちょっと有効方法が思いつきませんが、何かと使えそうですね。

WithTimeout 基本的使い方

WithTimeoutはSoftware Design (2019年2月号)Go言語のところの最後に書いてありました。
指定時間が経過したら関数をキャンセルします。
(2019/01/23 ctrl + c で止める必要はなさそうなのでその部分は削除しました。)

package main

import (
	"context"
	"fmt"
	"time"
)

func doSomething(ctx context.Context) {
	fmt.Println("before in doSomething")
	time.Sleep(5 * time.Second) // (2)
	fmt.Println("after  in doSomething")
}

func main() {
	ctx := context.Background()
	ctx, cancel := context.WithTimeout(ctx, 3*time.Second) // (1)
	defer cancel()

	go doSomething(ctx)

	select {
	case <-ctx.Done(): // (3)
		fmt.Println("done:", ctx.Err())
	}
}

Playgroundで動作確認してみる

実行結果

before in doSomething
// (2)doSomething で5秒待機が始まる
// (1)しかしWtithTimeoutの3秒待機後のキャンセルが早い
done: context deadline exceeded  // そのため(3)のチャネルが返る

WithTimeoutで指定時間が過ぎるとキャンセルすることができます。

WithTimeout 自分でキャンセル実行

ここまでのサンプルコードを自分で書いててふと疑問に思ったのですが、タイムアウト予定の関数(ここではdoSomething)が思いのほか早く終わってタイムアウト以内で終わってしまったらどうなるのか?どうしたらいいのか?を考えてみました。

  1. WithTimeoutで10秒タイムアウトを設定する。
  2. doSomethingで5秒待機に、doSomethingが終了。

っとなったら、すぐに次の処理に行ってほしいですよね。

サンプルコードでタイムアウト時間を単純に延ばしてやってみます。
(2019/01/23 ctrl + c で止める必要はなさそうなのでその部分は削除しました。)

package main

import (
	"context"
	"fmt"
	"time"
)

func doSomething(ctx context.Context) {
	fmt.Println("before in doSomething")
	time.Sleep(5 * time.Second)
	fmt.Println("after  in doSomething")
}

func main() {
	ctx := context.Background()
	// 10秒にしました。
	ctx, cancel := context.WithTimeout(ctx, 10*time.Second)
	defer cancel()

	go doSomething(ctx)

	select {
	case <-ctx.Done():
		fmt.Println("done:", ctx.Err())
	}
}

Playgroundで動作確認してみる

実行結果

before in doSomething
after  in doSomething     // doSomethingの5秒待機が終わる
// でもWithTimeoutのタイムアウト時間が10秒だから残り5(=10 - 5)秒間待たされる!
// タイムアウト以内で処理が終わるんならすぐに終わってほしい。。。
done: context deadline exceeded

ちょっと場合によってはよろしくない動作になりました。
おそらくWithTimeoutを設定した場合は一定時間内に処理が終わってほしいものの、すぐに終わるんであればすぐ次の動作に行ってほしいことが多いと思われます。例えばどこかのサーバーにデータを取りにって30秒以内にリクエストが返ってこなければタイムアウトしてしかたないけど、数秒でリクエストが返ってくるのであればタイムアウト時間まで待つ必要はありません。

これどうしたもんかな?っと考えてみました。

ググってみたところあまり見つからなかったので、こんなコーディングにしてみました。CancelFuncを関数に渡しています。

package main

import (
	"context"
	"fmt"
	"time"
)

// 第二引数にcancel context.CancelFunc
func doSomething(ctx context.Context, cancel context.CancelFunc) {
	fmt.Println("before in doSomething")
	time.Sleep(5 * time.Second)
	fmt.Println("after  in doSomething")

	cancel() // 終わったら自分でcancelする。
}

func main() {
	ctx := context.Background()
	ctx, cancel := context.WithTimeout(ctx, 10*time.Second)
	defer cancel()

	go doSomething(ctx, cancel) // cancel関数も一緒に渡す

	select {
	case <-ctx.Done():
		fmt.Println("done:", ctx.Err())
	}
}

Playgroundで動作確認してみる

実行結果

before in doSomething
after  in doSomething
done: context canceled   // doSomethingが終わったらすぐにcancel()するのでctx.Done()のチャンネルをキャッチ

defer cancel()がプログラム終了後に実行されてしまうんで、なんかモヤっとしてしまいます。けどもcontextをタイムアウトを無効化する方法がなさげなので、うーんまあこれでいいかなーと・・・・。

最後に

Go言語のgorotine,非同期処理はなかなか難しいですが知ってると柔軟な実装ができていいですね。

そんでふと以前読んだ「Go言語による並行処理」を見てみると今回の内容がほとんど書いてありますね!かなり飛ばし読みして途中でやめてしまったのが悔やまれます。必読の本であることを再確認しました。

Go言語による並行処理

Go言語による並行処理

  • 作者: Katherine Cox-Buday,山口能迪
  • 出版社/メーカー: オライリージャパン
  • 発売日: 2018/10/26
  • メディア: 単行本(ソフトカバー)
  • この商品を含むブログを見る
blog.suganoo.net

(こんな記事もあります)
blog.suganoo.net

【Go】go test でテストしよう

f:id:suganoo:20190111143332p:plain
Goでちゃんとテストコードを書こうとようやく重い腰をあげてみました。

コーディングしてるとテストコードは後回しにしてしまうんですよね。ちゃんとやってみます。後回しにするの良くないっすね(汗)。。。

go testについて調べたことを記事にしてみようと思います。

基本的な実行方法

サンプルコード

こんなコードを用意してみます。

hoge.go
hoge_test.go
fuga_test.go

hoge.go

package main

import (
        "fmt"
)

func hogefunc() int {
        return 1
}

func fugafunc() int {
        return 2
}

func main() {
        fmt.Println(hogefunc())
        fmt.Println(fugafunc())
}

hoge_test.go

package main

import (
        "testing"
)

func TestHogefunc(t *testing.T){
        n := hogefunc()
        if n != 1 {
                t.Fatal("Error return num")
        }
}

fuga_test.go

package main

import (
        "testing"
)

func TestFugafunc(t *testing.T){
        n := fugafunc()
        if n != 2 {
                t.Fatal("Error return num")
        }
}
注意点

気をつけたいところとして下記があります。

  • テストファイルのファイル名は「_test.go」で終わること。
  • テストコードのテスト関数は「TestXxxx」の形式にすること。

2番目の方は一度、「Testgethoge...」とか書いてしまいなんでテストが実行されないんだ?っと悩んでしまいました。正確に書きましょう「TestGethoge...」ですね。

実行とオプション

  • -v -coverつけておこう。

実行コマンドです。

go test

ですが、下記パラメータをオススメします。

go test -v -cover

っというのも出力結果が変わってくるからです。

なにもオプションをつけないとこうなります。

go test

PASS
ok      _/xxxxxxxxxxxx 0.293s

が、オプションつけるとこうなります。

go test -v -cover

=== RUN   TestFugafunc
--- PASS: TestFugafunc (0.00s)
=== RUN   TestHogefunc
--- PASS: TestHogefunc (0.00s)       <--- -v のおかげ
PASS
coverage: 50.0% of statements       <---- -coverのおかげ
ok      /xxxxxxxxxxxxxxx 0.296s

テスト関数の指定

特定のテスト関数だけを実行することも可能です

go test -v -cover -run TestHogefunc

ここなんですけど、じゃあ特定の2つのテスト関数やりたいときどうすればいいのか?と考えたんですが、あまりスマートにできそうにありませんでした。

どうも -run 以降は正規表現で指定するようで、特定の複数のテスト関数を指定したいときは正規表現で指定するしかないようです。

go test  -v -cover -run Test.*func

とか。

特定のテストファイルだけ

特定のテストファイルだけを実行したいときは下記のようにします。

go test -v -cover hoge_test.go hoge.go

この時 hoge.go ファイルを抜かしてしまうと下記のようなエラーになります。

go test -v -cover hoge_test.go
# command-line-arguments [command-line-arguments.test]
.\hoge_test.go:8:7: undefined: hogefunc
FAIL    command-line-arguments [build failed]

hogefunc関数がねーよと。

もっとオプション

様々なオプション

上記のオプションはごく一部でして、まだたくさんあります。
下記のコマンドを実行すればいろいろ出てきます。

go help testflag

coverage?

上記のcoverageの結果なんですが、ちょっと気になったところがありました。

coverage: 50.0% of statements    

50%ってどういうことなんだ?どこからこういう計算をしてるんでしょう?

下記のコマンドを実行するとcoverrageの結果をブラウザで確認することができます。

go test -v -coverprofile output
go tool cover -html=output -o cover.html

このcover.htmlをブラウザで確認してみます。
f:id:suganoo:20190111113026p:plain
なるほど、実行してる関数の内50%しか確認してないことがわかりますね。

引数ある場合どうする?

  • -args つける!

スクリプトを実行する際に引数を必要とすることもあります。

例えば、flagを使ってその引数にconfファイルを設定してて、そのファイルの中の値を取得する関数があったとします。テスト実行時に引数としてファイルが必要になりますよね。

そんなときには -args が使えます。

引数が必要なサンプルコード 引数無くても動いちゃうんだけど、そこは勘弁。。。

package main

import (
        "flag"
        "fmt"
        "os"
)

var (
        argC = flag.String("c", "default", "config file path")
)

func hogefunc() int {
        return 1
}

func fugafunc() int {
        return 2
}

func ini() {
        // read config file
        flag.Parse()
        conf := *argC
        fmt.Println(conf)
        if conf == "default" {
                os.Exit(1)
        }

}

func main() {
        fmt.Println(hogefunc())
        fmt.Println(fugafunc())
}

実行はこんな感じ

go run hoge.go -c conf.txt

テスト実行コマンドはこうなります。

go test -v -cover -args -c confighoge

最後

(SEOのために試しに書いてみる)「いかがでしたか?」

ここでも「Goプログラミング実践入門」が役に立ちました。
あの本の中にはUnitテストやHttpのテスト、ベンチマークテストも書かれており、ざっくりGoのテストを理解するためにかなり役立ちました。
けっこう優秀だな!

Goプログラミング実践入門 標準ライブラリでゼロからWebアプリを作る impress top gearシリーズ

Goプログラミング実践入門 標準ライブラリでゼロからWebアプリを作る impress top gearシリーズ

blog.suganoo.net
blog.suganoo.net


で、こんな記事を書いてて「みんなのGo言語」を立ち読みしたら、俺の知りたいことほとんど書いてあった。。。
ある程度知識や経験が溜まってくると「みんなのGo言語」はかなりいい本だなと再認識してきます。
オススメです。

みんなのGo言語【現場で使える実践テクニック】

みんなのGo言語【現場で使える実践テクニック】

【Go】templateの基礎的使い方

GoでWebプログラミングを学んでます。

今回はtemplateの使い方の紹介です。

基礎的な使い方

まずはソースコードから。
main.go

package main

import (
        "html/template"
        "net/http"
)

func process(w http.ResponseWriter, r *http.Request){
        t := template.Must(template.ParseFiles("tmpl.html"))
        m := "Helooooooo!!!" // <--- tmpl.html の {{ . }} に表示されるデータ
        t.Execute(w, m)
}

func main(){
        server := http.Server{
                Addr: "127.0.0.1:8080",
        }
        http.HandleFunc("/process", process)
        server.ListenAndServe()
}

tmpl.html

<!DOCTYPE html>
<html>
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
    <title>Go template work</title>
  </head>
  <body>
    {{ . }}  <!-- <--- ここにデータが表示される-->
  </body>
</html>

これを実行して localhost:8080/process をブラウザで確認すると

Helooooooo!!!

っと表示されます。

でこれは、文字列以外に数値でも構造体でも配列でもハッシュでも m:= ... のところでデータを渡せば表示することができます。

template.Must

余談ですがtemplate.ParseFilesの結果をtemplate.Mustでラップしてます。
これ知らなかったのですが、エラー処理を省略するためです。
template - The Go Programming Language
ParseFilesの結果はエラー結果も返してしまいますが、そのエラー処理を省略させてしまうのがMustです。
template - The Go Programming Language

構造体を渡す

とりあえず構造体を渡す

そんでこのデータを渡すときは構造体を渡してやると何かと使いやすいので構造体を使うことが多いです。
以降修正箇所は部分的に提示します。

type Member struct {
        Id   int
        Name string
}

func process(w http.ResponseWriter, r *http.Request){
        t := template.Must(template.ParseFiles("tmpl.html"))
        m := Member{   // <--- 構造体で渡す
                Id: 123,
                Name: "bob",
        }
        t.Execute(w, m)
}
  <body>
    {{ .Id }} <!-- 構造体MemberのIdを表示 -->
  </body>

構造体で配列を渡す

例えば下記のように構造体の中の配列を表示させるには、rangeを使うと表示できます。

type Member struct {
        Items   []Item
}
type Item struct {
        Id      int
        Name    string
}

func process(w http.ResponseWriter, r *http.Request){
        t := template.Must(template.ParseFiles("tmpl.html"))
        m := Member{
                Items: []Item{
                        Item{Id: 123, Name: "bob"},
                        Item{Id: 456, Name: "tom"},
                        Item{Id: 789, Name: "mike"},
                },
        }
        t.Execute(w, m)
}
  <body>
    <ul>
    {{ range .Items }}
       <li>{{ .Id }} - {{ .Name }}</li>
    {{ end }}
    </ul>
  </body>
・123 - bob
・456 - tom
・789 - mike

range は indexも使える

そしてこのrangeはindexも使えます。
main.goは変えずにtmpl.htmlを修正します。

  <body>
    <ul>
    {{ range .Items }}
       <li>{{ .Id }} - {{ .Name }}</li>
    {{ end }}
    </ul>
    <ul>
    {{ range $i, $v := .Items }}
       <li>{{ index $.Items $i }} : {{ $v }}</li>
    {{ end }}
    </ul>
  </body>
・123 - bob
・456 - tom
・789 - mike

・{123 bob} : {123 bob}
・{456 tom} : {456 tom}
・{789 mike} : {789 mike}

なかなか使えますね。

Goのwebhdfsクライアントを修正してみた

HadoopへファイルをGo言語でインポートしたくて、webhdfs経由で入れようとしていました。
そこでGoのクライアントライブラリーを使ってやってみたんだけど、少し苦労したって話です。

Go言語のwebhdfsクライアントってあんまりない。

Go言語でアプリを作ってて、webhdfsのクライアントになんかいいのないかなーと探してたら下記を見つけたのでこれでやってみることにした。
github.com
なんかアフリカ系のすごい笑顔がでてきたな。。。

これを探すまでにちょっと驚いたんだけどGo言語でHadoopのクライアントライブラリーってほとんど無いみたいね。
ここで探してもたった2つしかない。。。
webhdfs - Go libraries and applications
f:id:suganoo:20181227095445p:plain

まあそうだろうね、webhdfsのREST apiがあるから別に誰かのクライアント使わなくても自分でURLとかちゃんと書いてnet/http使えばなんとかなりそうだからな。
でも面倒なことしたくなかったのでクライアント使うことにしました。

エラーがでる。。。

READMEをもとにして書いてやってみたんですが、なんかエラーがでる。

import os
import github.com/vladimirvivien/gowfs
fs, err := gowfs.NewFileSystem(gowfs.Configuration{Addr: "hoge.com:14000", User: "hdfs"})
if err != nil{
	log.Fatal(err)
}
fp, _ := os.Open("hoge.txt.gz")
ok, err := fs.Create(
	fp,
	gowfs.Path{Name:"/hoge/hoge.txt.gz"},
	false,
	0,
	0,
	0700,
	0,
)
invalid character '<' looking for beginning of value

ソースのエラーのところをfmt.Printlnでいろいろと表示させて調べたところ、content-typeが適切に入ってないことがわかってきた。

Content-Type:[text/html;charset=utf-8]
 <u>Data upload requests must have content-type set to 'application/octet-stream'</u><

修正

なので仕方がないので、forkして修正してPRしてみた。
github.com

fs.Create()の末尾にcontent-type 入れたらそれを設定できるというもの。

ok, err := fs.Create(
	fp,
	gowfs.Path{Name:"/hoge/hoge.txt.gz"},
	false,
	0,
	0,
	0700,
	0,
	"application/octet-stream"   // <--- これな!
)

本家のコミットを見てみると、3,4年更新されてないからPRも取り込まれることもないでしょう。
なので動作確認はしたけどテストコードは試してない。。。
テストコード書いた方がいいんだけどなあ....
f:id:suganoo:20181227095617g:plain
お疲れ様でした。