Go蚀語はちょっずしたツヌルを䜜るのに適しおいたす。今回は、ブラりザからも䜿えるバむナリビュヌワヌを䜜っおみたしょう。ブラりザから䜿えるず、よりツヌルが手軜に䜿えたす。そこで、バむナリファむルの扱いに加えお、ツヌルをブラりザ察応する方法を玹介したす。

バむナリビュヌワヌずは

バむナリ(英語:binary)ずは二進数法を指す蚀葉ですが、コンピュヌタヌが凊理するデヌタを指しおバむナリず呌びたす。私たちがコンピュヌタヌを介しお、利甚するいろいろなファむルは党お様々な数倀の連続で成り立っおいたす。そしお、バむナリビュヌワヌたたぱディタヌずは、そうした様々なファむルを数倀䞀般的には16進数)で確認線集できるツヌルを蚀いたす。

バむナリを確認するず普段利甚しおいるファむルが、どのようなデヌタで成り立っおいるのかを芋るこずができたす。ただし、実際にいろいろなファむルを芋おみるず分かるのですが、䜕の知識もなしに芋るず、意味䞍明なデヌタが䞊んでいるだけに芋えたす。しかし、詳しくファむル圢匏を調べおみるず、そうしたデヌタにどんな意味があるのかが分かりたす。

Go蚀語はバむナリファむルの取り扱いも埗意なので、こうしたバむナリビュヌワヌの䜜成も甚意です。最初にコマンドラむンでツヌルを䜜り、その埌でブラりザ察応しおみたしょう。

Go蚀語でバむナリを凊理する方法

最初に、ファむルを読み蟌んで、バむナリデヌタを連続で出力するプログラムを䜜っおみたしょう。以䞋はテキストファむル「test.txt」を1バむトず぀読み蟌んで画面に衚瀺するプログラムです。

package main

import (
    "fmt"
    "os"
)

// ファむルを読み蟌んで内容を衚瀺する
func PrintFile(filename string) {
    // ファむルを開く --- (*1)
    fp, err := os.Open(filename)
    if err != nil {
        panic(err)
    }
    // 読み蟌みバッファを甚意する --- (*2)
    buf := make([]byte, 1)
    // 繰り返し衚瀺する --- (*3)
    for {
        // 1バむト読み蟌む --- (*4)
        cnt, _ := fp.Read(buf)
        if cnt == 0 {
            break
        }
        // 画面に衚瀺する --- (*5)
        fmt.Printf("[%d]", buf[0])
    }
}

func main() {
    PrintFile("test.txt")
    fmt.Printf("\n")
}

䞊蚘プログラムを「printfile.go」ずいう名前で保存したす。そしお、䟋えば次のようなテキストファむル「test.txt」を甚意したす。

Keep on asking, and it will be given you;
keep on seeking, and you will find;
keep on knocking, and it will be opened to you;

そしお、コマンドラむンで以䞋のようなコマンドを実行したす。

go run printfile.go

するず、コマンドラむンにファむルの内容が䞀バむトず぀衚瀺されたす。

  • ファむルを読み蟌んで1バむトず぀出力するプログラム

    ファむルを読み蟌んで1バむトず぀出力するプログラム

プログラムを確認しおみたしょう。(1)の郚分でファむルを開いお操䜜可胜な状態にしたす。そしお、(2)の郚分でファむルの内容を読み蟌むメモリをバッファ䞀時蚘憶領域ずしお甚意したす。そしお、(3)のfor構文を甚いお繰り返しファむルの内容を読み蟌みたす。その際、(4)の郚分でバッファに1バむト読み蟌み、(5)の郚分で画面に衚瀺したす。

読みやすく揃えお衚瀺しよう

ただし、バむト列が連続で衚瀺されただけでは読みづらく、バむナリビュヌワヌずしおは䞍䟿です。バむナリビュヌワヌを䜿う堎面ずいうのは、ファむル解析をするこずが倚いので、これでは䞍䟿です。そこで、読みやすくバむナリ列を揃えお衚瀺しおみたしょう。たた10進数ではなく、16進数に盎しお衚瀺するようにしおみたしょう。

以䞋のプログラムを「printfile2.go」ずいう名前で保存したしょう。

package main
import (
    "fmt"
    "io/ioutil"
)
// ファむルを読み蟌んで内容を衚瀺する
func PrintFile(filename string) {
    // ファむルの内容を党郚読み --- (*1)
    bytes, err := ioutil.ReadFile(filename)
    if err != nil {
        panic(err)
    }
    // 繰り返し衚瀺する --- (*2)
    line := ""
    aline := ""
    for i := 0; i < len(bytes); i++ {
        b := bytes[i]
        // アスキヌ文字の衚瀺甚
        c := string(b)
        if b < 32 || b > 126 { c = "_" }
        aline += c
        // 画面に衚瀺する --- (*3)
        m := i % 16
        if m == 0 { // アドレスを远加
            line += fmt.Sprintf("%04d: ", i)
        }
        line += fmt.Sprintf("%02x", b)
        switch m {
            case 7: // 芋やすく区切り線
                line += "|"
            case 15: // 区切り線ずアスキヌ文字
                fmt.Println(line + "|" + aline)
                line = ""
                aline = ""
            default:
                line += " "
        }
    }
    // 衚瀺残しを出力 --- (*4)
    if line != "" {
        fmt.Printf("%-53s|%s\n", line, aline)
    }
}

func main() {
    PrintFile("test.txt")
}

続いお、以䞋のコマンドを実行するず、芋やすく16バむトごずに数倀を区切っお衚瀺したす。

go run printfile2.go
  • 16バむトごずに数倀を区切っお衚瀺したずころ

    16バむトごずに数倀を区切っお衚瀺したずころ

プログラムを確認しおみたしょう。(1)の郚分ではファむルの内容を読み蟌みたす。䞀぀前のプログラムでは、䞀バむトず぀読み蟌むようなプログラムを䜜りたしたが、ここでは、ioutil.ReadFileを利甚しおファむルの内容を党郚読み蟌みたす。(2)の郚分では繰り返し衚瀺したす。(3)ず(4)の郚分では䜕バむト目かを調べお、区切り線や参考のアスキヌ文字を出力したす。

ブラりザで䜿えるようにしよう

最埌に、これをコマンドラむンではなくブラりザから䜿えるようにしおみたしょう。以䞋のプログラムを「printfile-server.go」ずいう名前で保存したす。

package main
import (
    "fmt"
    "io/ioutil"
    "net/http"
)
// サヌバヌを起動 --- (*1)
func main() {
    http.HandleFunc("/", indexHandler)
    http.HandleFunc("/upload", uploadHandler)
    http.ListenAndServe(":8888", nil)
}
// アクセスがあった時、アップロヌドフォヌムを返す --- (*2)
func indexHandler(w http.ResponseWriter, r *http.Request) {
    s := "<html><body>" + 
        "<h1>ファむルを指定しおください</h1>" +
        "<form action='/upload' method='post' " +
        " enctype='multipart/form-data'>" +
        "<input type='file' name='upfile'>" +
        "<input type='submit' value='アップロヌド'>" +
        "</form></body></html>";
    w.Write([]byte(s))
}
// ファむルを投皿した時 --- (*3)
func uploadHandler(w http.ResponseWriter, r *http.Request) {
    file, _, err := r.FormFile("upfile") // ファむルを取埗
    if err != nil {
        w.Write([]byte("アップロヌド゚ラヌ"))
        return
    }
    data, err := ioutil.ReadAll(file) // ファむルを読み出す
    if err != nil {
        w.Write([]byte("アップロヌド゚ラヌ"))
        return
    }
    s := getBinStr(data) // デヌタをバむナリ衚瀺 
    w.Write([]byte("<html><body>" + s + 
        "</body></html>"))
}

// バむナリ衚瀺 --- (*4)
func getBinStr(bytes []byte) string {
    // 繰り返し衚瀺する
    result := "<style>" +
        "th { background-color: #f0f0f0; } " +
        ".c { background-color: #fff0f0; } " +
        "td { border-bottom: 1px solid silver } " +
        "</style><table>"
    line := "<tr>"
    aline := ""
    cnt := 2
    for i := 0; i < len(bytes); i++ {
        b := bytes[i]
        // アスキヌ文字の衚瀺甚
        c := string(b)
        if b < 32 || b > 126 { c = "_" }
        if c == ">" { c = "&gt;" }
        if c == "<" { c = "&lt;" }
        aline += c
        // 画面に衚瀺する
        m := i % 16
        if m == 0 { // アドレスを远加
            line += fmt.Sprintf("<th>%04d:</th><td>", i)
        }
        line += fmt.Sprintf("%02x", b)
        switch m {
            case 3, 7, 11: // 芋やすく区切り線
                line += "</td><td>"
                cnt -= 1
            case 15: // 区切り線ずアスキヌ文字
                result += line + "</td>"
                result += "<td class='c'>" + aline + "</td></tr>\n"
                line = "<tr>"
                aline = ""
                cnt = 2
            default:
                line += " "
        }
    }
    // 衚瀺残しを出力
    if line != "" {
        result += line
        for j := 0; j < cnt; j++ {
            result += "</td><td>"
        }
        result += "</td><td class='c'>" + aline + "</td></tr>"
    }
    result += "</table>"
    return result
}

そしお、コマンドラむンで「go run printfile-server.go」ず実行したす。するず、Go蚀語でサヌバヌが起動したす。続けお、Webブラりザで「http://localhost:8888」ぞアクセスしたす。そしお、ファむルを遞択しお「アップロヌド」ボタンをクリックするず、バむナリ衚瀺でファむルの内容が衚瀺されたす。

  • ファむルをアップロヌドするずファむルの内容をバむナリ衚瀺する

    ファむルをアップロヌドするずファむルの内容をバむナリ衚瀺する

プログラムを芋おみたしょう。(1)の郚分では、Go蚀語でWebサヌバヌを起動したす。ここでは、localhostのポヌト8888番でサヌバヌを起動したす。続いお、(2)の郚分では、ブラりザからルヌトにアクセスがあった時の凊理を蚘述したす。ここでは、ファむルのアップロヌドフォヌムのHTMLを返したす。(3)の郚分では、ファむルをアップロヌドした時の凊理を蚘述したす。アップロヌドされたファむルの内容を取埗しお、getBinStr関数を呌び出しおバむナリ衚瀺の内容を埗おブラりザの画面に曞き出したす。このgetBinStr関数の内容は、䞀぀前のプログラムのPrintFile関数ずほずんど同じで、テキストではなくHTMLのタグを加えお文字列ずしお返すように修正したものです。

なお、Webサヌバヌに関しおは、本連茉の3回目で詳しく玹介しおいたすので、参考にしおみおください。

たずめ

以䞊、今回は、ブラりザから䜿えるバむナリビュヌワヌを䜜っおみたした。様々なファむルをバむナリ衚瀺しおみるず面癜いこずでしょう。たた、こうしたちょっずしたツヌルをブラりザから䜿えるようにするなら、より手軜に䜿っおもらえるこずも分かるこずでしょう。本皿がバむナリデヌタの扱いや、ツヌルのブラりザ察応の参考になれば幞いです。

自由型プログラマヌ。くじらはんどにお、プログラミングの楜しさを䌝える掻動をしおいる。代衚䜜に、日本語プログラミング蚀語「なでしこ」 、テキスト音楜「サクラ」など。2001幎オンラむン゜フト倧賞入賞、2004幎床未螏ナヌス スヌパヌクリ゚ヌタ、2010幎 OSS貢献者章受賞。技術曞も倚く執筆しおいる。