go可执行程序携带附件

jan-bar · 2019-03-22 09:47:34 · 1069 次点击 · 预计阅读时间 4 分钟 · 大约8小时之前 开始浏览    
这是一个创建于 2019-03-22 09:47:34 的文章,其中的信息可能已经有所发展或是发生改变。

1.灵感来源

主要是看到这个开源项目:
https://github.com/jteeuwen/go-bindata
该项目就是将文件生成go代码,编译出来的可执行程序可以创建携带的文件。我之前有个工具需要7za.exe,因为怕别人的电脑上没有这个文件,所以我想在我代码中判断环境不存在7za.exe或md5值不正确的时候,自动生成正确的7za.exe。当时就是使用上面的库,但是我仔细研究了一下源码发现生成的go源文件还是有点大,而且我也不需要太多复杂功能,因此我自己写了一个简化版的代码。

2.放上源码
package main

import (
    "compress/zlib"
    "fmt"
    "io"
    "log"
    "os"
    "path/filepath"
    "strings"
)

func main() {
    if len(os.Args) != 2 {
        fmt.Printf("Usage: %s file\n", os.Args[0])
        return
    }

    fName := os.Args[1]
    fInfo, err := os.Stat(fName)
    if (err != nil && os.IsNotExist(err)) || fInfo.IsDir() {
        log.Fatal("file not find or is dir.") // 不存在 或 是文件夹,报错
    }

    fr, err := os.Open(fName)
    if err != nil {
        log.Fatal(err)
    }
    defer fr.Close()

    fw, err := os.Create(fName + ".go")
    if err != nil {
        log.Fatal(err)
    }
    defer fw.Close()

    tExt := "_new" + filepath.Ext(fName)
    tName := strings.Replace(filepath.Base(fName), ".", "_", -1)
    fmt.Fprintf(fw, `package main

import (
    "bytes"
    "compress/zlib"
    "io"
    "log"
    "os"
)

func main() {
    fw, err := os.Create(`+"`%s`"+`)
    if err != nil {
        log.Fatal(err)
    }
    defer fw.Close()

    data, err := Read_%s()
    if err != nil {
        log.Fatal(err)
    }
    fw.Write(data)
}

func Read_%s() ([]byte, error) {
    data := []byte("`, fName+tExt, tName, tName)

    zw := zlib.NewWriter(&StringWriter{w: fw})
    if _, err = io.Copy(zw, fr); err != nil {
        log.Fatal(err)
    }

    if err = zw.Close(); err != nil {
        log.Fatal(err)
    }

    fmt.Fprint(fw, `")
    zr, err := zlib.NewReader(NewStringReader(data))
    if err != nil {
        return nil, err
    }

    var out bytes.Buffer
    if _, err = io.Copy(&out, zr); err != nil {
        return nil, err
    }

    if err = zr.Close(); err != nil {
        return nil, err
    }
    return out.Bytes(), nil
}

type StringReader struct {
    buf []byte
    off int
}

func fromHexChar(c byte) byte {
    switch {
    case '0' <= c && c <= '9':
        return c - '0'
    case 'a' <= c && c <= 'f':
        return c - 'a' + 10
    }
    return 0
}

func NewStringReader(p []byte) *StringReader {
    n := len(p) / 2
    for i := 0; i < n; i++ {
        p[i] = fromHexChar(p[2*i])<<4 | fromHexChar(p[2*i+1])
    }
    return &StringReader{buf: p[:n], off: 0}
}

func (sr *StringReader) Read(p []byte) (int, error) {
    if len(sr.buf) <= sr.off {
        if len(p) == 0 {
            return 0, nil
        }
        return 0, io.EOF
    }
    n := copy(p, sr.buf[sr.off:])
    sr.off += n
    return n, nil
}`)
}

type StringWriter struct {
    w io.Writer
}

func (sw *StringWriter) Write(p []byte) (n int, err error) {
    if len(p) == 0 {
        return
    }

    var (
        b        byte
        buf      = make([]byte, 2)
        lowerHex = "0123456789abcdef"
    )
    for n, b = range p {
        buf[0] = lowerHex[b>>4]
        buf[1] = lowerHex[b&0xf]
        sw.w.Write(buf)
    }
    n++
    return
}
3.讲解用法
1.我的工具用法
# .\pack.exe 7za.exe
2.开源go-bindata用法
go-bindata.exe 7za.exe
3.按照大小排序文件
# ls -lth
total 6.5M
-rw-r--r-- 1 * 197121 1.5M 3月  22 09:25 bindata.go
-rw-r--r-- 1 * 197121 755K 3月  22 09:25 7za.exe.go
-rwxr-xr-x 1 * 197121 2.3M 3月  22 09:09 pack.exe*
-rwxr-xr-x 1 * 197121 661K 11月  9 18:16 7za.exe*
4.分析
bindata.go是开源工具产生的文件
7za.exe.go是我的工具产生的文件,明显我产生的文件要小很多,原因是开源工具存储的[]byte每个字节转"\x12",而我的直接就是"12"少了2字节数据,会是我生成的2倍。
5.还原文件
 go run 7za.exe.go
 diff 7za.exe 7za.exe_new.exe
 执行生成的go文件,会得到一个还原的文件,将还原的文件和源文件对比没有差别,完美!
4.总结

做这个主要场景就是方便使用者,无需关心附件的问题。有些软件会将附件和可执行程序打成压缩包,这也是一种解决方案,就看怎么权衡了。
特别注意:对于那种已经压缩过的文件可能生成的文件不会压缩太多,这个自行百度为什么。像".zip,.mp3,.exe"等已经压缩过的文件如果使用zlib压缩后效果不明显则还是建议不要再次压缩了,毕竟效果不好还浪费解压时间。


有疑问加站长微信联系(非本文作者))

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

1069 次点击  ∙  1 赞  
加入收藏 微博
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传