Golang Cafe #17 まとめ osサブパッケージ

2014/02/16に開催された「Golang Cafe #17」についてのまとめです。
今回は、os.execパッケージos.signalパッケージos.userパッケージをまとめておきます。


+TakashiYokoyama氏が準備してくれたサンプルはexecsamplesignalsampleusersample、になります。
私のサンプルは、execsignaluserになります。

os.execパッケージ

os.execパッケージはプログラム中から外部プロセスを起動したり、起動したプロセスの終了を待機したり、起動したプロセスからの出力を取得するためのパッケージです。
+TakashiYokoyama氏のサンプルをWindowsコマンド用に変更してみました。

import (
    "fmt"
    "io/ioutil"
    "os/exec"

    "code.google.com/p/go.text/encoding/japanese"
    "code.google.com/p/go.text/transform"
)

func main() {
    path, err := exec.LookPath("cmd")
    if err != nil {
        fmt.Printf("Error %v\n", err)
        return
    }

    fmt.Printf("Path = %s\n", path)

    cmd := exec.Command("cmd", "/c", "dir", "c:\\golang\\go\\")

    stdoutpipe, err := cmd.StdoutPipe()
    if err != nil {
        fmt.Printf("StdoutPipe Error: %v\n", err)
        return
    }
    defer stdoutpipe.Close()

    err = cmd.Start()
    if err != nil {
        fmt.Printf("Command Start Error: %v\n", err)
        return
    }

    stdout, err := ioutil.ReadAll(
        transform.NewReader(stdoutpipe, japanese.ShiftJIS.NewDecoder()))
    if err != nil {
        fmt.Printf("Command Error: %v\n", err)
        return
    }

    err = cmd.Wait()
    if err != nil {
        fmt.Printf("Command Wait Error: %v\n", err)
        return
    }

    fmt.Printf("%s\n", stdout)
}
$ go run sample01.go
Path = c:\Windows\system32\cmd.exe
 ドライブ C のボリューム ラベルがありません。
 ボリューム シリアル番号は FED1-AC65 です

 c:\golang\go のディレクトリ

2013/12/13  11:45              .
2013/12/13  11:45              ..
2013/12/13  11:45              api
2013/11/28  21:54            13,577 AUTHORS
2013/12/13  11:45              bin
2013/12/13  11:45              blog
2013/11/28  21:54            19,578 CONTRIBUTORS
2013/12/13  11:45              doc
2013/11/28  21:54             1,150 favicon.ico
2013/12/13  11:45              include
2013/12/13  11:45              lib
2013/11/28  21:54             1,479 LICENSE
2013/12/13  11:45              misc
2013/11/28  21:54             1,303 PATENTS
2013/12/13  11:45              pkg
2013/11/28  21:54             1,112 README
2013/11/28  21:54                26 robots.txt
2013/12/13  11:45              src
2013/12/13  11:45              test
2013/11/28  21:56                 5 VERSION
               8 個のファイル              38,230 バイト
              12 個のディレクトリ  25,666,240,512 バイトの空き領域

手順としては、

  1. exec.Command関数で実行するコマンドを定義します。
  2. 起動したコマンドからのアウトプットを受け取る場合には、StdoutPipeメソッドにてコマンドを実行する前にReaderを取得しておきます。
  3. StartメソッドまたはRunメソッドでコマンドを実行します。両者の違いはStartメソッドはこのメソッドを実行後処理を続行します。Runメソッドは実行したコマンドが終了するまで待機します。
  4. Startメソッドでコマンドを実行後、終了を待機するにはWaitメソッドを使用します。

Runメソッド使用時はStdoutPipeは使えません。

os.signalパッケージ

os.signalパッケージは指定したシグナルを受信するためのパッケージです。
+TakashiYokoyama氏のサンプルで説明します。

func main() {
    c := make(chan os.Signal)

    signal.Notify(c, os.Interrupt, syscall.SIGHUP)

    tc := time.After(5 * time.Second)

    // シグナルを受信していなければ、関数を抜ける。
    defer signal.Stop(c)
    for {
        select {
        case s := <-c:
            fmt.Printf("Signal Receive: %v\n", s)
            if s == os.Interrupt {
                return
            }
        case <- tc:
            // Windowsにはsyscall.Kill()が定義されていないので、コンパイルエラーになる。
            // (Windows用のソースファイルが無い!)
            // syscallパッケージに定義されている、Signalの定義も怪しいかも?
            syscall.Kill(syscall.Getpid(), syscall.SIGHUP)
        }
    }
}

使い方は非常に簡単でSignal型のチャンネルを用意しておき、Notify関数で受信するシグナルを設定するだけです。
シグナルを受信するとチャンネルに値が設定されるのでこれを参照するようにします。
シグナルの受信を中止する場合はStop関数を実行します。
ただ、このサンプルはソースコード中のコメントにもあるようにWindows環境では動作しません。Kill関数が動作しません、というかWindows用にはソースコードレベルで関数自体が定義されていないようです。

os.userパッケージ

os.userパッケージはユーザアカウントの情報を取得するためのパッケージです。
こちらも+TakashiYokoyama氏のサンプルを私の環境用に変更したもので説明します。

func main() {
    u, err := user.Current()
    if err != nil {
        fmt.Println("Error: ", err)
        return
    }

    printf("Current User", u)

    u, err = user.LookupId("S-1-5-21-XXXXXX...........")
    if err != nil {
        fmt.Println("Error: ", err)
        return
    }

    printf("LookupId User", u)

    u, err = user.Lookup("hogeuser")
    if err != nil {
        fmt.Println("Error: ", err)
        return
    }

    printf("Lookup User", u)

    fmt.Printf("Getuid: %v\n", os.Getuid())
    fmt.Printf("Getgid: %v\n", os.Getgid())
}

func printf(method string, u *user.User) {
    fmt.Printf("#%s#\nUid: %s\nGid: %s\nUsername: %s\nName: %s\nHomeDir: %s\n\n",
        method, u.Uid, u.Gid, u.Username, u.Name, u.HomeDir)
}
$ go run sample01.go
#Current User#
Uid: S-1-5-21-XXXXXX...........
Gid: S-1-5-21-XXXXXX...........
Username: hoge-PC\hogeuser
Name:
HomeDir: C:\Users\taknb2nch

#LookupId User#
Uid: S-1-5-21-XXXXXX...........
Gid: unknown
Username: hoge-PC\hogeuser
Name:
HomeDir: Unknown directory

#Lookup User#
Uid: S-1-5-21-XXXXXX...........
Gid: unknown
Username: hoge-PC\hogeuser
Name:
HomeDir: Unknown directory

Getuid: -1
Getgid: -1

それぞれCurrent関数は現在のユーザ、LookupId関数はユーザIDを指定して取得、Lookup関数はユーザ名を指定して取得します。


以前Windows環境でos.Getuid関数やos.Getgid関数を試した時、強制的にreturn -1という実装だったので今回もダメかと思っていましたが、予想とは逆に取得することができるようです。
ただUser.Nameが取得できなかったり、関数によって取得できる内容が異なっているようです。


Go言語は簡単にクロスコンパイルができるとのことなので、userパッケージWindowsXP 32bitではどうかと試したばっかりにまたハマってしまいました。
結果的には取得できたのですがそれまでにはいつもの様に苦労がありました。
詳しくは別エントリーでまとめます。

まとめ

やはりWindows環境では動かなかったり、すべての結果が取得できなかったりと辛い結果もありましたが、内容的には難しいものはありませんでした。
次回はいつもとは趣向をかえてgoauth2を試してみるそうです。