2014年2月17日月曜日

Golang Cafe #17を開催しました。

Golang Cafe #17を開催しました。
今回のサンプルコードはgithubにおいてありますのでごらんください。

今回は、os/exec、os/signal、os/userパッケージを見ていきました。
その中で、Windows版だと、動かない現象があったので、メモを残しておきます。
(来週の土曜日まで残しておくと、記憶が薄れそうなので)

サンプルコードのコメントにも書きましたが、syscall.Kill()がWindowsでは、定義されていないため、コンパイルエラーになります。こういうOSの仕組みがまったく異なっていることによるソースコードの差分でしょうが、以前の"return -1"ではなく、定義さえもされないということで、signalsample2.goはWindowsではコンパイルもできないということになります。
(今後、こういう問題は改善されるのだろうか…?)

execパッケージのポイント


Command構造体


execパッケージのサンプルのポイントは、Command構造体で、execするプロセスを管理するのですが、標準入力、標準出力、標準エラー出力にPipeを使って、アクセスすることができます。
それぞれ、StdoutPipe()StdinPipe()StderrPipe()を使ってio.ReadCloser()、io.WriteCloser()を受け取り、io/ioutilパッケージを利用するなどして読み取ります。

Start()でプロセスの起動、Wait()でプロセスの終了を待つ


exec.Command("コマンド名", パラメータ...)でCommand構造体を生成し、cmd.Start()でプロセスが起動します。Pipeで標準出力などにアクセスする場合は、必ず、Start()の呼び出しよりも前にStdoutPipe()を呼び出しておく必要があります。Start()の後に、呼び出すと、errorが返されてしまい、アクセスすることはできません。また、Start()を呼び出さずに、Wait()だけを呼び出してしまっても、無限待ちになってしまいますのでStart()→Wait()の順番は守るようにして下さい。
Start()はブロックするわけではなく、そのまま処理を続行します。もし、プロセスの終了をその場で待ちたい場合は、cmd.Run()を呼び出すと、内部でStart()→Run()の順番で呼び出しますので、ブロックされます。

ブロックして、標準出力を得るのであれば、Output()


コマンドの終了を待ち、しかも出力結果も受け取りたい場合は、
exec.Command().Output()とすると、戻り値に[]byteとして、標準出力の結果を受け取ることができます。また、exec.Command().CombinedOutput()にすると、
標準出力と、標準エラー出力がマージされた状態(恐らく、出力された順番)で[]byteを受け取ることができます。
この辺りは、実行して監視するコマンドによって使い分けが必要でしょう。

Signalは受け取るのみ可能

os/signalパッケージは、Notify()とStop()だけが定義されています。Notify()は外部からSignalを受け取った時に、引数で指定した、Channelに対して、受け取ったSignal構造体を送信します。Channelに送信されるSignalは同じく、引数で指定したSignalのみとなります。(サンプルコードでどんなものか、確認してみてください)

Stop()はChannelに格納されているSignal構造体を全てキャンセルする関数で、何もなければ、すぐに終了します。したがって、「Signalが送られていないことを保証する」事ができます。プログラムを終了するときなどに呼び出すもの。と思えばいいと思います。

Windows版はAPIに依存しているos/userパッケージ


user.Current()は実行ユーザを取得、user.Lookup("アカウント")は指定したユーザを取得、user.LookupId(uid)はユーザIDからユーザを取得します。
Mac/Linuxは、この説明で問題はないのですが、Windows版はkernel32.dllをLoadlibrary()した挙句に、Win32API(?)をコールしてAPIの動作に依存したコードになっています。(DLLの読み込みなどは、cgoが使われています。unsafeパッケージの使い方などは、いい勉強になるかも知れません)
(システムに依存しているのでどうにもならないと思いますが…。今後大丈夫なんだろうか…?)

実際User構造体はuid、gid、アカウント名、ホームディレクトリ、ユーザのフルネームなどが格納されますが、uid、gidは、GetTokenInformation()を使って、SIDを取得しています。uidの方は、TokenUserで指定し、gidはTokenPrimaryGroupで指定した結果を受け取ってそれぞれ設定していました。
ホームディレクトリは、GetUserProfileDirectory()が使われていました。
ユーザ名とフルネームについては、(Golang Cafeの最中には途中でやめたのですが、せっかくなので読みました。)ドメイン/ワークグループに参加しているかどうかを、NetGetJoinInformation()を呼び出し、結果が取得できるかどうかを確認しています。
ActiveDirectoryとかそういうのに参加している場合はこのAPIがエラーを返すのでしょう…。恐らく。
ドメイン/ワークグループに参加している場合は、TranslateName()を呼び出し、情報を取得します。ActiveDirectoryの場合は、NetUserGetInfo()を呼び出し情報を取得します。
その結果から、フルネームとユーザ名を取得し、UsernameとNameにセットされるという流れになります。

結果として、Windows版は大丈夫か?と言いたくなる程度にはWin32APIに依存しています。今後、.NETFrameworkの機能しか提供されなくなったらかなりまずそうです…。

ということで、Golangの標準パッケージを使うというのもひと通り見たなということで、
次回は、goauth2パッケージを使ってGoogle Apps APIを叩くという内容になります。

サンプルコードで挙動を確認するという段階は十分やったと思うので、今後は、実際に
コードを書き、"Goらしいコードの書き方"というのを追求できたらと思っています。