内容目录
命令行界面(CLI)是纯文本的,比如最常用的ls
,tar
等。而go就是设计这种程序的理想选择,比如我们熟知的Kubernetes
,Docker
等。Go可以非常快速地编译为一个二进制文件,可以以一致的风格跨平台工作。用Go编写的程序可以在任何系统上运行,而无需任何现有的库,运行时或依赖项。
当然了,我们不会从零开始,有很多强大的库供我们使用,比如:
- spf13/cobra,用于创建功能强大的现代CLI应用程序的库,以及用于在Go中生成应用程序和CLI应用程序的程序
- spf13/viper, 是Go应用程序的完整配置解决方案,旨在在应用程序内工作以处理配置需求和格式
- urfave/cli,这是用于创建和组织命令行Go应用程序的最小框架
- delve,一个简单而强大的工具,为程序员习惯于以编译语言使用源代码级调试器
- chzyer/readline,一个纯Golang实现,在GNU Readline中提供了大多数功能(在MIT许可下)
- dixonwille/wmenu,是CLI应用程序的一种易于使用的菜单结构,提示用户做出选择
- spf13/pflag,Go的
flag
包的替代品,实现POSIX/GNU样式的标志
这里我们使用urface/cli来实现一个简单的命令:apk-info -p ./x.apk -lm
来查看apk的信息
说明,这里借助了aapt
工具,这个请自行搜索安装哈。
package main
import (
"fmt"
"github.com/urfave/cli/v2"
"log"
"os"
)
var (
path string
long bool
human bool
)
func main() {
app := &cli.App{
Name: "apk-info", // 应用名称
Usage: "show apk info", // 功能说明
UseShortOptionHandling: true, // 使用短参数处理,比如 ls -lh 中的-lh形式
// 参数列表
Flags: []cli.Flag{
&cli.PathFlag{
Name: "path", // 参数名称,这里是apk的路径
Aliases: []string{"p"}, // 参数的别名
Usage: "apk `path`", // 参数说明
Required: true, // 是否必须得
Destination: &path, // 参数赋值给path变量
},
&cli.BoolFlag{
Name: "long",
Aliases: []string{"l"},
Value: false,
DefaultText: "false", // 默认值
Usage: "show apk more `info`",
Destination: &long,
},
&cli.BoolFlag{
Name: "human",
Aliases: []string{"m"},
Value: false,
DefaultText: "false", // 默认值
Usage: "human-readable", // 以人类刻度的形式展示,比如12MB
Destination: &human,
},
},
// 应用执行函数
Action: func(context *cli.Context) error {
info, err := aaptApkInfo(path)
if err != nil {
log.Fatal(err)
}
// 打印执行结果
fmt.Print(info.String(human, long))
return nil
},
}
// 运行应用
if err := app.Run(os.Args); err != nil {
log.Fatal(err)
}
}
package main
import (
"bufio"
"crypto/md5"
"encoding/hex"
"fmt"
"io"
"os"
"os/exec"
"strconv"
"strings"
)
type fileSize int64
const (
pkgNamePrefix = "package: name='"
sdkVersionPrefix = "sdkVersion:'"
targetSdkVersion = "targetSdkVersion:'"
permissionPrefix = "uses-permission: name='"
cpName1Prefix = "application: label='"
cpNamePrefix = "application-label:'"
cpNameZhPrefix = "application-label-zh:'"
)
type apkInfo struct {
CpName string
PkgName string
VersionCode int
VersionName string
SdkVersion int
TargetSdkVersion int
Permissions map[string]struct{}
Size fileSize
Md5 string
}
// 字节的单位转换 保留一位小数
func (fs fileSize) format() string {
fileSize := float64(fs)
switch {
case fileSize < 1024:
return fmt.Sprintf("%.1fB", fileSize)
case fileSize < (1024 * 1024):
return fmt.Sprintf("%.1fKB", fileSize/1024)
case fileSize < (1024 * 1024 * 1024):
return fmt.Sprintf("%.1fMB", fileSize/(1024*1024))
default:
return fmt.Sprintf("%.1fGB", fileSize/(1024*1024*1024))
}
}
func (info *apkInfo) permissionStr() string {
elems := info.Permissions
switch len(elems) {
case 0:
return ""
case 1:
for key, _ := range elems {
return key
}
}
const sep = "|"
n := len(sep) * (len(elems) - 1)
for key, _ := range elems {
n += len(key)
}
var b strings.Builder
b.Grow(n)
first := true
for key, _ := range elems {
if first {
first = false
b.WriteString(key)
continue
}
b.WriteString(sep)
b.WriteString(key)
}
return b.String()
}
func (info *apkInfo) String(human, long bool) string {
const format = "%-16s: %v\n"
var b strings.Builder
b.WriteString(fmt.Sprintf(format, "appName", info.CpName))
b.WriteString(fmt.Sprintf(format, "pkgName", info.PkgName))
b.WriteString(fmt.Sprintf(format, "md5", info.Md5))
if human {
b.WriteString(fmt.Sprintf(format, "size", info.Size.format()))
} else {
b.WriteString(fmt.Sprintf(format, "size", info.Size))
}
b.WriteString(fmt.Sprintf(format, "versionName", info.VersionName))
b.WriteString(fmt.Sprintf(format, "versionCode", info.VersionCode))
b.WriteString(fmt.Sprintf(format, "sdkVersion", info.SdkVersion))
b.WriteString(fmt.Sprintf(format, "targetSdkVersion", info.TargetSdkVersion))
if long {
b.WriteString(fmt.Sprintf(format, "permissions", info.permissionStr()))
}
return b.String()
}
func aaptApkInfo(apkPath string) (*apkInfo, error) {
file, err := os.Open(apkPath)
if err != nil {
return nil, err
}
cmd := exec.Command("aapt", "d", "badging", apkPath)
out, err := cmd.StdoutPipe()
if err != nil {
return nil, err
}
if err := cmd.Start(); err != nil {
return nil, err
}
reader := bufio.NewReader(out)
info := new(apkInfo)
md5 := md5.New()
size, err := io.Copy(md5, file)
if err != nil {
return nil, err
}
info.Size = fileSize(size)
info.Md5 = hex.EncodeToString(md5.Sum(nil))
info.Permissions = make(map[string]struct{})
var cpNameZh, cpName1 string
for {
line, _, err := reader.ReadLine()
if err != nil {
if err == io.EOF {
break
}
return nil, err
}
s := string(line)
switch {
case strings.HasPrefix(s, pkgNamePrefix): // 包名 版本 版本号
s = s[len(pkgNamePrefix):]
info.PkgName = s[:strings.Index(s, "'")]
s = s[len(info.PkgName)+15:]
versionCodeS := s[:strings.Index(s, "'")]
info.VersionCode, err = strconv.Atoi(versionCodeS)
if err != nil {
return nil, err
}
s = s[len(versionCodeS)+15:]
info.VersionName = s[:strings.Index(s, "'")]
case strings.HasPrefix(s, sdkVersionPrefix): // SDK版本
s = s[len(sdkVersionPrefix):]
sdkVersionS := s[:strings.Index(s, "'")]
info.SdkVersion, err = strconv.Atoi(sdkVersionS)
if err != nil {
return nil, err
}
case strings.HasPrefix(s, targetSdkVersion): // SDK版本
s = s[len(targetSdkVersion):]
targetSdkVersion := s[:strings.Index(s, "'")]
info.TargetSdkVersion, err = strconv.Atoi(targetSdkVersion)
if err != nil {
return nil, err
}
case strings.HasPrefix(s, permissionPrefix): // 权限
s = s[len(permissionPrefix):]
s = s[:strings.Index(s, "'")]
info.Permissions[s] = struct{}{}
case strings.HasPrefix(s, cpNamePrefix): // 应用名称
s = s[len(cpNamePrefix):]
info.CpName = s[:strings.Index(s, "'")]
case strings.HasPrefix(s, cpNameZhPrefix): // 应用名称
s = s[len(cpNameZhPrefix):]
cpNameZh = s[:strings.Index(s, "'")]
case strings.HasPrefix(s, cpName1Prefix): // 应用名称
s = s[len(cpName1Prefix):]
cpName1 = s[:strings.Index(s, "'")]
}
if cpNameZh != "" {
info.CpName = cpNameZh
}
if info.CpName == "" && cpName1 != "" {
info.CpName = cpName1
}
}
return info, nil
}
go build 后咱们看下使用结果:
apk-info -h
NAME:
apk-info - show apk info
USAGE:
apk-info [global options] command [command options] [arguments...]
COMMANDS:
help, h Shows a list of commands or help for one command
GLOBAL OPTIONS:
--path path, -p path apk path
--long info, -l info show apk more info (default: false)
--human, -m human-readable (default: false)
--help, -h show help (default: false)
apk-info -p ./20191101142847_745_2588.apk
appName : 源来生活
pkgName : com.orangedream.sourcelife
md5 : 5bdd79ce23f6c1406f04d37fd393ddb8
size : 12020323
versionName : 1.0.1
versionCode : 101
sdkVersion : 18
targetSdkVersion: 28
apk-info -p ./20191101142847_745_2588.apk -lm
appName : 源来生活
pkgName : com.orangedream.sourcelife
md5 : 5bdd79ce23f6c1406f04d37fd393ddb8
size : 11.5MB
versionName : 1.0.1
versionCode : 101
sdkVersion : 18
targetSdkVersion: 28
permissions : android.permission.SYSTEM_ALERT_WINDOW|android.REQUEST_INSTALL_PACKAGES.GET_TASKS|android.permission.READ_SETTINGS
0 条评论
撰写评论