Skip to content

IM10-命令行解析

上一章的 IP 和 端口 都是写死的,那能不能像命令 nc 127.0.0.1 8088 那样,把 IP 和 端口 命令参数传进去呢?

这就用到了 flag 这个包了。

如果你想自己写一个像 ls、cd、tar 这样的命令,那一定要好好掌握 flag 包。

解析命令行其实有两种方法:

  1. 通过 os.Args 获得所有参数,在通过切片得到每一个参数
  2. 通过 flag 包获得

解析方法

os.Args

os.Args 是一个字符串切片 []string,用法非常简单:

package main

import (
    "fmt"
    "os"
)

func main() {
    for i, arg := range os.Args {
        fmt.Printf("args[%d] is %v \n", i, arg)
    }
}

运行:

$ go build -o echo_args

$ ./echo_args 1 2 B o i i

args[0] is ./echo_args
args[1] is 1
args[2] is 2
args[3] is B
args[4] is o
args[5] is i
args[6] is i

flag

flag 包提供了 T()系列、TVar()系列。

  • T()系列基本格式:flag.T(参数名, 默认值, 帮助信息) *T
  • 具体有以下几种:
    • flag.Bool()
    • flag.String()
    • flag.Int()
    • flag.Int64()
    • flag.Duration()
    • flag.Float64()
    • flag.Uint()
    • flag.Uint64()
  • TVat()系列基本格式:flag.StringVar(&T, 参数名, 默认值, 帮助信息)
  • 具体有以下几种:
    • flag.BoolVar()
    • flag.StringVar()
    • flag.IntVar()
    • flag.Int64Var()
    • flag.DurationVar()
    • flag.Float64Var()
    • flag.UintVar()
    • flag.Uint64Var()
  • 区别:
    • T()系列返回一个 T型指针,需要一个变量去承接
    • TVar()系列不返回,变量是作为参数传进去的。

举例:

func main() {
    // T() 系列
    name := flag.String("n", "Boii", "请输入名字")

    // TVar() 系列
    var sex string
    flag.StringVar(&sex, "s", "男", "请输入性别")

    flag.Parse()    // 得调用 Parse 才能解析命令行参数

    fmt.Println("name is", *name)
    fmt.Println("sex is", sex)
}

运行:

$ go build -o flag_args

$ ./flag_args -n Eva -s=
name is Eva
sex is 女

flag 形式

通过 flag 包这两个系列接收的命令行参数,在命令中可以使用以下4种方式:

  1. -flag xxx
  2. -flag=xxx(等号两边不能有空格,布尔类型参数必须用等号的方式)
  3. --flag xxx
  4. --flag=xxx(等号两边不能有空格,布尔类型参数必须用等号的方式)
$ ./flag_args -n Eva --s 女

$ ./flag_args -n=Eva --s=

帮助信息

通过 -h、--h-help、--help 可以获得帮助信息。

$ ./flag_args --help

Usage of ./flag_args:
  -n string
        请输入名字 (default "Boii")
  -s string
        请输入性别 (default "男")

$ ./flag_args -h

Usage of ./flag_args:
  -n string
        请输入名字 (default "Boii")
  -s string
        请输入性别 (default "男")

改造客户端

说完两种解析方式,接下来开始让客户端具有解析功能

使用第一种os.Args的方式:

package main

import (
    "os"
    "log"
    "strconv"
)

// $ ./client -ip 127.0.0.1 -port 8088
func main() {
    // 读取参数
    serverIP := os.Args[1]
    serverPort, _ := strconv.Atoi(os.Args[2])

    // 启动客户端连接服务器
    client := NewClient(serverIP, serverPort)
    if client == nil {
        log.Println(">>>>> 连接服务器失败...")
        return
    }
    log.Println(">>>>> 连接服务器成功...")

    select {}
}

使用第二种flag的方式:

package main

import (
    "flag"
    "log"
)

var serverIP string
var serverPort int

// $ ./client -ip 127.0.0.1 -port 8088
func init() {
    // 读取参数
    flag.StringVar(&serverIP, "ip", "127.0.0.1", "设置服务器IP地址")
    flag.IntVar(&serverPort, "port", 80, "设置服务器端口")
    flag.Parse()
}

func main() {
    // 启动客户端连接服务器
    client := NewClient(serverIP, serverPort)
    if client == nil {
        log.Println(">>>>> 连接服务器失败...")
        return
    }
    log.Println(">>>>> 连接服务器成功...")

    select {}
}
这里将 flag 解析的部分放在 init() 函数中,因为读取命令行参数只会做一次,而且 init() 是先于 main() 运行的,所以比较规范。

编译运行

$ go build -o client ./main.go ./client.go

$ ./client -ip 127.0.0.1 -port 8088

运行效果