Goならわかるシステムプログラミング:第3章

  • 標準入力
package main

import (
    "os"
    "io"
    "fmt"
)

func main() {
    for {
        buffer := make([]byte, 5)
        size, err := os.Stdin.Read(buffer)
        if err == io.EOF {
            fmt.Println("EOF")
            break
        }
        fmt.Printf("size=%d input='%s'\n", size, string(buffer))
    }
}
  • ファイル入力
package main

import (
    "os"
    "io"
)

func main() {
    file, err := os.Open("main.go")
    if err != nil {
        panic(err)
    }
    defer file.Close()
    io.Copy(os.Stdout, file)
}
  • ネットワーク通信の読み込み
package main

import (
    "net"
    "net/http"
    "bufio"
    "fmt"
    "io"
    "os"
)

func main() {
    conn, err := net.Dial("tcp", "ascii.jp:80")
    if err != nil {
        panic(err)
    }
    conn.Write([]byte("GET / HTTP/1.0\r\nHost: ascii.jp\r\n\r\n"))
    res, err := http.ReadResponse(bufio.NewReader(conn), nil)
    fmt.Println(res.Header)
    defer res.Body.Close()
    io.Copy(os.Stdout, res.Body)

}
package main

import (
    "bytes"
    "encoding/binary"
    "fmt"
)

func main() {
    data := []byte{0x0, 0x0, 0x27, 0x10}
    var i int32
    binary.Read(bytes.NewReader(data), binary.BigEndian, &i)
    fmt.Printf("data: %d\n", i)
}
  • PNGファイルを分析してみる
package main

import (
    "io"
    "encoding/binary"
    "fmt"
    "os"
)

func dumpChunk(chunk io.Reader) {
    var length int32
    binary.Read(chunk, binary.BigEndian, &length)
    buffer := make([]byte, 4)
    chunk.Read(buffer)
    fmt.Printf("chunk, '%v' (%d bytes)\n", string(buffer), length)
}

func readChunks(file *os.File) []io.Reader {
    var chunks []io.Reader
    file.Seek(8, 0)
    var offset int64 = 8

    for {
        var length int32
        err := binary.Read(file, binary.BigEndian, &length)
        if err == io.EOF {
            break
        }
        chunks = append(chunks, io.NewSectionReader(file, offset, int64(length)+12))
        offset, _ = file.Seek(int64(length+8), 1)
    }
    return chunks
}

func main() {
    file, err := os.Open("Lenna.png")
    if err != nil {
        panic(err)
    }
    defer file.Close()
    chunks := readChunks(file)

    for _, chunk := range chunks {
        dumpChunk(chunk)
    }
}

実行結果

chunk, 'IHDR' (13 bytes)
chunk, 'sRGB' (1 bytes)
chunk, 'IDAT' (473761 bytes)
chunk, 'IEND' (0 bytes)

Process finished with exit code 0
  • PNG画像に秘密のテキストを入れてみる
package main

import (
    "io"
    "bytes"
    "encoding/binary"
    "hash/crc32"
    "os"
    "fmt"
)

func textChunk(text string) io.Reader {
    byteData := []byte(text)
    var buffer bytes.Buffer
    binary.Write(&buffer, binary.BigEndian, int32(len(byteData)))
    buffer.WriteString("tExt")
    buffer.Write(byteData)
    crc := crc32.NewIEEE()
    io.WriteString(crc, "tExt")
    binary.Write(&buffer, binary.BigEndian, crc.Sum32())
    return &buffer
}
func readChunks(file *os.File) []io.Reader {
    var chunks []io.Reader
    file.Seek(8, 0)
    var offset int64 = 8

    for {
        var length int32
        err := binary.Read(file, binary.BigEndian, &length)
        if err != nil {
            break
        }
        chunks = append(chunks, io.NewSectionReader(file, offset, int64(length)+12))
        offset , _ = file.Seek(int64(length+8), 1)
    }
    return chunks
}

func dumpChunk(chunk io.Reader) {
    var length int32
    binary.Read(chunk, binary.BigEndian, &length)
    buffer := make([]byte, 4)
    chunk.Read(buffer)
    fmt.Printf("chunk '%v' (%d bytes\n", string(buffer), length)
    if bytes.Equal(buffer, []byte("tExt")) {
        rawText := make([]byte, length)
        chunk.Read(rawText)
        fmt.Println(string(rawText))
    }
}

func writeFile() {
    file, err := os.Open("Lenna.png")
    if err != nil {
        panic(err)
    }
    defer file.Close()
    newFile, err := os.Create("Lenna2.png")
    if err != nil {
        panic(err)
    }
    chunks := readChunks(file)
    io.WriteString(newFile, "\x89PNG\r\n\x1a\n")
    io.Copy(newFile, chunks[0])
    io.Copy(newFile, textChunk("ASCII PROGRAMMING++"))
    for _, chunk := range chunks[1:] {
        io.Copy(newFile, chunk)
    }
}

func readFile() {
    file, err := os.Open("Lenna2.png")
    if err != nil {
        panic(err)
    }
    defer file.Close()
    chunks := readChunks(file)
    for _, chunk := range chunks {
        dumpChunk(chunk)
    }
}

func main() {
    writeFile()
    readFile()
}

  • テキスト解析 改行/単語で区切る
package main

import (
    "bufio"
    "strings"
    "fmt"
    "io"
)

var source = `1行目
2行目
3行目`

func main() {
    reader :=bufio.NewReader(strings.NewReader(source))
    for {
        line, err := reader.ReadString('\n')
        fmt.Printf("%#v\n", line)
        if err == io.EOF {
            break
        }
    }
}
  • bufio.Scannerを使う
package main

import (
    "bufio"
    "strings"
    "fmt"
)

var source = `1行目
2行目
3行目`

func main() {
    scanner := bufio.NewScanner(strings.NewReader(source))
    for scanner.Scan() {
        fmt.Printf("%#v\n", scanner.Text())
    }
}
  • データ型を指定して解析
package main

import (
    "strings"
    "fmt"
)

var source = "123 1.234 1.0e4 test"

func main() {
    reader := strings.NewReader(source)
    var i int
    var f, g float64
    var s string
    fmt.Fscan(reader, &i, &f, &g, &s)
    fmt.Printf("i=%#v f=%#v g=%#v s=%#v\n", i, f, g, s)
}

実行結果

i=123 f=1.234 g=10000 s="test"

Fscanはスペース(または改行)で区切られたstringを読み込んで2つ目以降の引数の変数に代入していく。 

  • csvフォーマットの文字列の解析
package main

import (
    "strings"
    "encoding/csv"
    "io"
    "fmt"
)

var csvSource =
    `13101, "100 ", "1000003", "トウキョウト", "チヨダク", "ヒトツバシ(1チョウメ)", "東京都", "千代田区", "一ツ橋(1丁目)", 1,0,1,0,0,0 
13101, "101  ", "1000003", "トウキョウト", "チヨダク", "ヒトツバシ(2チョウメ)", "東京都", "千代田区", "一ツ橋(2丁目)", 1,0,1,0,0,0
13101, "100  ", "1000012", "トウキョウト", "チヨダク", "ヒビヤコウエン", "東京都", "千代田区", "日比谷公園", 0,0,0,0,0,0
13101, "102  ", "1000093", "トウキョウト", "チヨダク", "ヒトツバシ(1チョウメ)", "東京都", "千代田区", "平河町", 0,0,1,0,0,0
13101, "102  ", "1000071", "トウキョウト", "チヨダク", "ヒトツバシ(1チョウメ)", "東京都", "千代田区", "富士見", 0,0,1,0,0,0
`

func main() {
    reader := strings.NewReader(csvSource)
    csvReader := csv.NewReader(reader)
    csvReader.LazyQuotes = true
    for {
        line, err := csvReader.Read()
        if err == io.EOF {
            break
        }
        fmt.Println(line[2], line[6:9])
    }
}

実行結果

 "1000003" [ "東京都"  "千代田区"  "一ツ橋(1丁目)"]
 "1000003" [ "東京都"  "千代田区"  "一ツ橋(2丁目)"]
 "1000012" [ "東京都"  "千代田区"  "日比谷公園"]
 "1000093" [ "東京都"  "千代田区"  "平河町"]
 "1000071" [ "東京都"  "千代田区"  "富士見"]

BOM付UTF-8にはまった。書籍そのままでは動かない。

GoでBOM付きのUTF8なCSVを扱うには | tail -f pinzo.log

encoding/csv: bare " in non-quoted-field · Issue #21672 · golang/go · GitHub

  • io.Reader/io.Writerでストリームを自由に操る