命令行参数(flag包)

命令行参数

命令行参数可以直接通过 os.Args 获取,另外标准库的 flag 包专门用于接收和解除命令行参数

os.Args

简单的只是从命令行获取一个或一组参数,可以直接使用 os.Args。下面的这种写法,无需进行判断,无论是否提供了命令行参数,或者提供了多个,都可以处理:

// 把命令行参数,依次打印,每行一个
func main() {
    for _, s := range os.Args[1:] {
        fmt.Println(s)
    }
}

flag 基本使用

下面的例子使用了两种形式的调用方法:

package main

import (
    "flag"
    "fmt"
)

var name string

func init() {
    flag.StringVar(&name, "name", "Adam", "名字")
}

var ageP = flag.Int("age", 18, "年龄")

func main() {
    flag.Parse()
    fmt.Printf("%T %[1]v\n", name)
    fmt.Printf("%T %[1]v\n", ageP)
    fmt.Printf("%T %[1]v\n", *ageP)
}

第一种是直接把变量的指针传递给函数作为第一个参数,函数内部会对该变量进行赋值。这种形式必须写在一个函数体的内部。
第二种是函数会把数据的指针作为函数的返回值返回,这种形式就是给变量赋值,不需要现在函数体内,不过拿到的返回值是指针。

解析时间

时间长度类的命令行标志应用广泛,这个功能内置到了 flag 包中。
先看看源码中的示例,之后在自定义命令行标志的时候也能有个参考。下面的示例,实现了暂停指定时间的功能:

var period = flag.Duration("period", 1*time.Second, "sleep period")

func main() {
    flag.Parse()
    fmt.Printf("Sleeping for %v...", *period)
    time.Sleep(*period)
    fmt.Println()
}

默认是1秒,但是可以通过参数来控制。flag.Duration函数创建了一个 *time.Duration 类型的标志变量,并且允许用户用一种友好的方式来指定时长。就是用 String 方法对应的记录方法。这种对称的设计提供了一个良好的用户接口。

PS H:\Go\src\gopl\ch7\sleep> go run main.go -period 3s
Sleeping for 3s...
PS H:\Go\src\gopl\ch7\sleep> go run main.go -period 1m
Sleeping for 1m0s...
PS H:\Go\src\gopl\ch7\sleep> go run main.go -period 1.5h
Sleeping for 1h30m0s...

自定义类型

更多的情况下,是需要自己实现接口来进行自定义的。

接口说明

支持自定义类型,需要定义一个满足 flag.Value 接口的类型:

package flag

// Value 接口代表了存储在标志内的值
type Value interface {
    String() string
    Set(string) error
}

String 方法用于格式化标志对应的值,可用于输出命令行帮助消息。
Set 方法解析了传入的字符串参数并更新标志值。可以认为 Set 方法是 String 方法的逆操作,这两个方法使用同样的记法规格是一个很好的实践。

自定义温度解析

下面定义 celsiusFlag 类型来允许在参数中使用摄氏温度或华氏温度。因为 Celsius 类型原本就已经实现了 String 方法,这里把 Celsius 内嵌到了 celsiusFlag 结构体中,这样结构体有就有了 String 方法(外围结构体类型不仅获取了匿名成员的内部变量,还有相关方法)。所以为了满足接口,只须再定一个 Set 方法:

type Celsius float64
type Fahrenheit float64

func CToF(c Celsius) Fahrenheit { return Fahrenheit(c*9.0/5.0 + 32.0) }
func FToC(f Fahrenheit) Celsius { return Celsius((f - 32.0) * 5.0 / 9.0) }

func (c Celsius) String() string { return fmt.Sprintf("%g°C", c) }
// 上面这些都是之前在别处定义过的内容,是可以作为包引出过来的
// 为了说明清楚,就单独把需要用到的部分复制过来

// *celsiusFlag 满足 flag.Vulue 接口
type celsiusFlag struct{ Celsius }

func (f *celsiusFlag) Set(s string) error {
    var unit string
    var value float64
    fmt.Sscanf(s, "%f%s", &value, &unit) // 无须检查错误
    switch unit {
    case "C", "°C":
        f.Celsius = Celsius(value)
        return nil
    case "F", "°F":
        f.Celsius = FToC(Fahrenheit(value))
        return nil
    }
    return fmt.Errorf("invalid temperature %q", s)
}

fmt.Sscanf 函数用于从输入 s 解析一个浮点值和一个字符串。通常是需要检查错误的,但是这里如果出错,后面的 switch 里的条件也是无法满足的,是可以通过switch之后的错误处理来一并进行处理的。
这里还需要写一个 CelsiusFlag 函数来封装上面的逻辑。这个函数返回了一个 Celsius 的指针,它指向嵌入在 celsiusFlag 变量 f 中的一个字段。Celsius 字段在标志处理过程中会发生变化(经由Set
方法)。调用 Var 方法可以把这个标志加入到程序的命令行标记集合中,即全局变量 flag.CommandLine。如果一个程序有非常复杂的命令行接口,那么单个全局变量就不够用了,需要多个类似的变量来支撑。最后一节“创建私有命令参数容器”会做简单的展开,不过也没有实现到这个程度。
调用 Var 方法是会把 *celsiusFlag 实参赋给 flag.Value 形参,编译器会在此时检查 *celsiusFlag 类型是否有 flag.Value 所必需的方法:

func CelsiusFlag(name string, value Celsius, usage string) *Celsius {
    f := celsiusFlag{value}
    flag.CommandLine.Var(&f, name, usage)
    return &f.Celsius
}

现在就可以在程序中使用这个标志了,使用代码如下:

var temp = CelsiusFlag("temp", 20.0, "温度")

func main() {
    flag.Parse()
    fmt.Println(*temp)
}

接下来还可以把上面的例子简单改一下,不用结构体了,而是换成变量的别名,这样就需要额外再实现一个String方法,完整的代码如下:

package main

import (
    "flag"
    "fmt"
)

type Celsius float64
type Fahrenheit float64

func CToF(c Celsius) Fahrenheit { return Fahrenheit(c*9.0/5.0 + 32.0) }
func FToC(f Fahrenheit) Celsius { return Celsius((f - 32.0) * 5.0 / 9.0) }

func (c Celsius) String() string { return fmt.Sprintf("%g°C", c) }

// 上面这些都是之前定义过的内容,是可以作为包引出过来的
// 为了说明清楚,就单独把需要用到的部分复制过来

// *celsiusValue 满足 flag.Vulue 接口
// 同一个包不必这么麻烦,直接定义 Celsius 类型即可。这里假设是从别的包引入的类型
type celsiusValue Celsius

func (c *celsiusValue) String() string { return fmt.Sprintf("%.2f°C", *c) }
// func (c *celsiusValue) String() string { return (*Celsius)(c).String() }

func (c *celsiusValue) Set(s string) error {
    var unit string
    var value float64
    fmt.Sscanf(s, "%f%s", &value, &unit) // 无须检查错误
    switch unit {
    case "C", "°C":
        *c = celsiusValue(value)
        return nil
    case "F", "°F":
        *c = celsiusValue(FToC(Fahrenheit(value)))
        return nil
    }
    return fmt.Errorf("invalid temperature %q", s)
}

func CelsiusFlag(name string, value Celsius, usage string) *Celsius {
    p := new(Celsius) // value 是传值进来的,取不到地址,new一个内存空间,存放value的值
    *p = value
    flag.CommandLine.Var((*celsiusValue)(p), name, usage)
    return p
}

func main() {
    tempP := CelsiusFlag("temp", 36.7, "温度")
    flag.Parse()
    fmt.Printf("%T, %[1]v\n", tempP)
}

打印默认值

使用上面最后一个例子,打印帮助,查看默认值的提示:

PS G:\Steed\Documents\Go\src\gopl\output\flag\tempconv2> go run main.go -h
Usage of C:\Users\Steed\AppData\Local\Temp\go-build840446178\b001\exe\main.exe:
  -temp value
        温度 (default 36.70°C)
exit status 2
PS G:\Steed\Documents\Go\src\gopl\output\flag\tempconv2> go run main.go -temp 36.7C
*main.Celsius, 36.7°C
PS G:\Steed\Documents\Go\src\gopl\output\flag\tempconv2>

默认值打印的格式和打印只的格式是有区别的,这是因为类型不同,调用了不同的 String 方法。
这里默认值显示的格式是根据接口类型的String方法定义的,在这里就是 *celsiusValue 类型的String方法。而后面打印的是 Celsius 类型,使用的是 Celsius 类型的 String 方法。这里定义了两个String方法,但是打印的效果又不同,显示不统一,这样的做法不够好。这里可以看出两个问题:

  1. 最初,使用结构体匿名封装的形式,避免了重复定义 String 方法。这样就保证了自定义的结构体类型 celsiusFlag 的String方法就是原本的 Celsius 类型的String方法。
  2. 帮助消息中打印的默认值,实际是打印自定义类型的值。而自定义类型只在flag包中有用,解析完成后使用的都是原本的类型,这里就是 Celsius 类型。这两个类型的String方法最好能保持一致。

所以,使用结构体封装应该是一种不错的实现方式。不过flag包中的 time.Duration 类型用的就是类型别名来实现的:

type durationValue time.Duration

func (d *durationValue) Set(s string) error {
    v, err := time.ParseDuration(s)
    *d = durationValue(v)
    return err
}

func (d *durationValue) String() string { return (*time.Duration)(d).String() }

上面是源码中的部分代码,可以看出这里保持一致的方法是进行类型转换后,调用原来类型的String方法。可能原本定义的是值类型的String方法,也可能直接就是定义了指针类型的String方法,不过指针类型的方法包括了所有值类型的方法,所以这里不必关系原本类型的方法具体是指针方法还是值方法。
所以最后一个示例中的String方法也可以做同样的修改:

func (c *celsiusValue) String() string { return (*Celsius)(f).String() }

自定义切片

以字符串切片为例,这里有两种实现的思路。一种是直接提供一个字符串,然后做分隔得到切片:

package main

import (
    "flag"
    "fmt"
    "strings"
)

type fullName []string

func (v *fullName) String() string {
    r := []string{}
    for _, s := range *v {
        r = append(r, fmt.Sprintf("%q", s))
    }
    return strings.Join(r, " ")
}

func (v *fullName) Set(s string) error {
    *v = nil
    // strings.Fields 可以区分连续的空格
    for _, str := range strings.Fields(s) {
        *v = append(*v, str)
    }
    return nil
}

func FullName(name string, value []string, usage string) *[]string {
    p := new([]string) // value 是传值进来的,取不到地址,new一个内存空间,存放value的值
    *p = value
    flag.CommandLine.Var((*fullName)(p), name, usage)
    return p
}

func main() {
    s := FullName("name", []string{"Karl", "Lichter", "Von", "Randoll"}, "全名")
    flag.Parse()
    fmt.Printf("% q\n", *s)
}

这里就不管 String 方法和 Set 方法展示规格的一致了,String方法采用 %q 的输出形式可以更好的把每一个元素清楚的展示出来。

还有一种方式是,可以多次调用同一个参数,每一次调用,就添加一个元素:

package main

import (
    "flag"
    "fmt"
    "strings"
)

type urls []string

func (v *urls) String() string {
    r := []string{}
    for _, s := range *v {
        r = append(r, fmt.Sprintf("%q", s))
    }
    return strings.Join(r, ", ")
}

func (v *urls) Set(s string) error {
    // *v = nil // 不能再清空原有的记录了
    // strings.Fields 可以区分连续的空格
    *v = append(*v, s)
    return nil
}

func Urls(name string, value []string, usage string) *[]string {
    p := new([]string) // value 是传值进来的,取不到地址,new一个内存空间,存放value的值
    *p = value
    flag.CommandLine.Var((*urls)(p), name, usage)
    return p
}

func main() {
    s := Urls("url", []string{"baidu.com"}, "域名")
    flag.Parse()
    fmt.Printf("% q\n", *s)
}

由于每出现一个参数,都会调用一次 Set 方法,所以只要在 Set 里对切片进行append就可以了。不过这也带来一个问题,就是默认值无法被覆盖掉:

PS G:\Steed\Documents\Go\src\gopl\output\flag\urls> go run main.go -h
Usage of C:\Users\Steed\AppData\Local\Temp\go-build727433198\b001\exe\main.exe:
  -url value
        域名 (default "baidu.com")
exit status 2
PS G:\Steed\Documents\Go\src\gopl\output\flag\urls> go run main.go
["baidu.com"]
PS G:\Steed\Documents\Go\src\gopl\output\flag\urls> go run main.go -url shuxun.net -url 51cto.com
["baidu.com" "shuxun.net" "51cto.com"]
PS G:\Steed\Documents\Go\src\gopl\output\flag\urls>

下面这个版本的Set方法引入了一个全局变量,可以改进上面的问题:

var isNew bool
func (v *urls) Set(s string) error {
    if !isNew {
        *v = nil
        isNew = true
    }
    *v = append(*v, s)
    return nil
}

这里是一个方法,无法改成闭包。最好的做法就是将这个变量和原本的字符串切片封装为一个结构体:

type urls struct {
    data  []string
    isNew bool
}

剩下的修改,参考之前自定义温度解析的实现就差不多了。

简易的自定义版本

要实现自定义类型,只需要实现接口就可以了。不过上面的例子中都额外写了一个函数,用于返回自定义类型的指针,并且还设置了默认值。这个方法内部也是调用 Var 方法。这里可以直接使用 flag 包里的 Var 函数调用全局的Var方法:

package main

import (
    "flag"
    "fmt"
    "strings"
)

type urls []string

func (v *urls) String() string {
    // *v = []string{"baidu.com"} // 通过指针改变初始值
    r := []string{}
    for _, s := range *v {
        r = append(r, fmt.Sprintf("%q", s))
    }
    return strings.Join(r, ", ")
}

var isNew bool
func (v *urls) Set(s string) error {
    if !isNew {
        *v = nil
        isNew = true
    }
    *v = append(*v, s)
    return nil
}

func main() {
    var value urls
    // value = append(value, "baidu.com") // 传递给Var函数前就设定好初始值
    flag.Var(&value, "url", "域名")
    flag.Parse()
    fmt.Printf("%T % [1]q\n", value)
    s := []string(value)
    fmt.Printf("%T % [1]q\n", s)
}

这里提供了两个设置初始值的方法,示例中都注释掉了。
String 方法由于内部是获得指针的,所以可以对变量进行修改。并且该方法调用的时机是在解析开始时只调用一次。所以在 String 方法里设置默认值是可行的。不过无法在打印帮助的时候把默认值打印出来。不需要这么做,但是正好可以对String方法有进一步的了解,还有就是这里利用指针修改参数原值的思路。
另外,由于 Var 函数需要接收一个变量,所以在定义变量的时候,就可以赋一个初始值。并且在打印帮助的时候是可以把这个初始值打印出来的。
不过简易版本最大的问题就是 Var 函数接收和返回的值都是 Value 接口类型。所以在使用之前,需要对返回值做一次类型转换。而设置初始值也是对 Value 接口类型的值进行设置。主要问题就是对外暴露了 Value 类型。现在调用者必须知道并且使用 Value 类型,对 Value 类型进行处理,这样就不是很友好。而之前的示例中,调用方(就是main函数中的那些代码)是完全可以忽略 Value 的存在的。
小结:这一小段主要是为了说明,之前示例中额外定义的函数是非常好的做法,封装了 flag 内部接口的细节。经过这个函数封装后再提供给用户使用,用户就可以完全忽略 flag.Value 这个接口而直接操作真正需要的类型了。这个函数的作用就是封装接口的所有细节,调用者只需要关注真正需要的操作的类型。

自定义命令参数容器

接下来就是通过包提供的方法行进一步的自定制。以下3小节是一层一层更加接近底层的调用,做更加深入的定制。

定制 Usage

回到最基本的使用,打印一下帮助消息可以得到以下的内容:

PS H:\Go\src\gopl\output\flag\beginning> go run main.go -h
Usage of C:\Users\Steed\AppData\Local\Temp\go-build926710106\b001\exe\main.exe:
  -age int
        年龄 (default 18)
  -name string
        名字 (default "Adam")
exit status 2
PS H:\Go\src\gopl\output\flag\beginning>

这里关注第一行,在 Usage of 后面是一长串的路径,这个是go run命令在构建上述命令源码文件时临时生成的可执行文件的完整路径。如果是编译之后再执行,就是可执行文件的相对路径,就没那么难看了。
这一行的内容也是可以自定制的,但是首先来看看源码里的实现:

var Usage = func() {
    fmt.Fprintf(CommandLine.Output(), "Usage of %s:\n", os.Args[0])
    PrintDefaults()
}

func (f *FlagSet) Output() io.Writer {
    if f.output == nil {
        return os.Stderr
    }
    return f.output
}

看上面的代码就清楚了,输出的内容就是执行的命令本身 os.Args[0]。就会输出的位置默认就是标准错误 os.Stderr。
这个 Usage 是可导出的变量,值是一个匿名函数,只要重新为 Usage 赋一个新值就可以完成内容的自定制:

var name string

func init() {
    flag.StringVar(&name, "name", "Adam", "名字")
    flag.Usage = func() {
        fmt.Fprintln(os.Stderr, "请指定名字和年龄:")
        flag.PrintDefaults()
    }
}

var ageP = flag.Int("age", 18, "年龄")

func main() {
    flag.Parse()
    fmt.Printf("%T %[1]v\n", name)
    fmt.Printf("%T %[1]v\n", ageP)
    fmt.Printf("%T %[1]v\n", *ageP)
}

只要在 flag.Parse() 执行前覆盖掉 flag.Usage 即可。
下面那行 flag.PrintDefaults() 则是打印帮助信息中其他的内容。完全可以把这行去掉,这里完全可以自定义打印更多其他内容,甚至是执行其他操作。

定制 CommandLine

在调用flag包中的一些函数(比如StringVar、Parse等等)的时候,实际上是在调用flag.CommandLine变量的对应方法。
flag.CommandLine相当于默认情况下的命令参数容?。通过对flag.CommandLine重新赋值,就可以更深层次地定制当前命令源码文件的参数使用说明。
flag包提供了NewFlagSet函数用于创建自定制的 CommandLine 。在上一个简单例子的基础上,修改一下其中的init函数的内容:

var name string
var age int

func init() {
    flag.CommandLine = flag.NewFlagSet("", flag.ExitOnError)
    flag.StringVar(&name, "name", "Adam", "名字")
    age = *flag.Int("age", 18, "年龄")
    // 和上面两句效果一样
    // flag.CommandLine.StringVar(&name, "name", "Adam", "名字")
    // var ageP = flag.CommandLine.Int("age", 18, "年龄")
    flag.CommandLine.Usage = func() {
        fmt.Fprintln(os.Stderr, "请指定名字和年龄:")
        flag.PrintDefaults()
    }
}

其实这里只加了一行语句。所有flag包的操作都要在flag.NewFlagSet执行之后,否则之前执行的内容会被覆盖掉。所以这里把flag.Int的调用移到了包内,否则在全局中的赋值语句会在这之前就运行了,然后被flag.NewFlagSet方法覆盖掉。
这里无论是 flag.StringVar 或者是 flag.CommandLine.StringVar,最终都是使用flag.NewFlagSet创建的 *FlagSet 对象的方法来调用的。不过本质上是有差别的:

  • flag.StringVar : 使用默认 CommandLine 的对象调用,但是第一行语句 flag.CommandLine = flag.NewFlagSet("", flag.ExitOnError) 则是把它的值覆盖为新创建的对象。
  • flag.CommandLine.StringVar : 使用 flag.NewFlagSet 函数创建的对象来调用,所以和上面是一个东西。

第一个方式是专门为默认的容器提供的便捷调用方式。第二个是则是通用的方法,之后创建私有命令参数容器的时候就需要用通用的方式来调用了。
Usage 必须用flag.CommandLine调用。另外不定制的话,包里也准备了默认的方法可以使用:

func (f *FlagSet) defaultUsage() {
    if f.name == "" {
        fmt.Fprintf(f.Output(), "Usage:\n")
    } else {
        fmt.Fprintf(f.Output(), "Usage of %s:\n", f.name)
    }
    f.PrintDefaults()
}

第一个参数的作用基本就是显示一个名称,也可以用空字符串,向上面这样。而第二个参数可以是下面三种常量:

const (
    ContinueOnError ErrorHandling = iota // Return a descriptive error.
    ExitOnError                          // Call os.Exit(2).
    PanicOnError                         // Call panic with a descriptive error.
)

效果一看就明白了。定义在解析遇到问题后,是执行何种操作。默认的就是ExitOnError,所以在--help执行打印说明后,最后一行会出现“exit status 2”,以状态码2退出。这里可以根据需要定制为抛出Panic。
使用-h参数打印帮助信息也算是解析出错,如果是Panic则会在打印帮助信息后Panic,如果是Continue则先打印帮助信息然后按照默认值执行。所以如果要使用另外两种模式,最好修改一下-h参数的行为,就是上面讲的定制Usage。使用-h参数之后程序将执行的就是Usage指定的函数。

创建私有命令参数容器

上一个例子依然是使用flag包提供的命令参数容器,只是重新进行了创建和赋值。这里依然是调用flag.NewFlagSet()函数创建命令参数容器,不过这次赋值给自定义的变量:

package main

import (
    "flag"
    "fmt"
    "os"
)

var cmdLine = flag.NewFlagSet("", flag.ExitOnError)
var name string
var age int

func init() {
    cmdLine.StringVar(&name, "name", "Adam", "名字")
    age = *cmdLine.Int("age", 18, "年龄")
}

func main() {
    cmdLine.Parse(os.Args[1:])
    fmt.Printf("%T %[1]v\n", name)
    fmt.Printf("%T %[1]v\n", age)
}

首先通过 flag.NewFlagSet 函数创建了私有的命令参数容器。然后调用其他方法的接收者都使用这个容器。另外还有很多方法可以调用,可以继续探索。

原文地址:https://blog.51cto.com/steed/2363801

时间: 2024-10-09 16:15:20

命令行参数(flag包)的相关文章

flag 是Go 标准库提供的解析命令行参数的包QANDA.REN文库

flag flag 是Go 标准库提供的解析命令行参数的包. 使用方式: flag.Type(name, defValue, usage) 其中Type为String, Int, Bool等:并返回一个相应类型的指针. flag.TypeVar(&flagvar, name, defValue, usage) 将flag绑定到一个变量上. 自定义flag 只要实现flag.Value接口即可: type Value interface { String() string Set(string)

go语言之行--文件操作、命令行参数、序列化与反序列化详解

一.简介 文件操作对于我们来说也是非常常用的,在python中使用open函数来对文件进行操作,而在go语言中我们使用os.File对文件进行操作. 二.终端读写 操作终端句柄常量 os.Stdin: 标准输入 os.Stdout: 标准输出 os.Stderr: 标准错误输出 读写示例: package main import ( "fmt" "os" ) var( username,password string ) func main() { fmt.Prin

VLC命令行参数详解

VLC命令行参数详解 2012-11-29 14:00 6859人阅读 评论(0) 收藏 举报 Usage: vlc [options] [stream] ...You can specify multiple streams on the commandline. They will be enqueued in the playlist.The first item specified will be played first. Options-styles:  --option  A gl

Go(day7 [终端读写| 文件操作 | 命令行参数 | Json序列化])

终端读写 操作终端相关文件句柄常量 os.Stdin:标准输入 os.Stdout:标准输出 os.Stderr:标准错误输出 终端读写示例: //Sscanf 是从变量中读取值 package main import "fmt" var ( firstName,lastName ,s string i int f float32 input = "56.12 / 5212 / Go" format  = "%f/%d/%s" ) func ma

聊聊默认支持的各种配置源[内存变量,环境变量和命令行参数]

聊聊默认支持的各种配置源[内存变量,环境变量和命令行参数] 较之传统通过App.config和Web.config这两个XML文件承载的配置系统,.NET Core采用的这个全新的配置模型的最大一个优势就是针对多种不同配置源的支持.我们可以将内存变量.命令行参数.环境变量和物理文件作为原始配置数据的来源,如果采用物理文件作为配置源,我们可以选择不同的格式(比如XML.JSON和INI等) .如果这些默认支持的配置源形式还不能满足你的需求,我们还可以通过注册自定义ConfigurationSour

mysql命令行参数

一,mysql命令行参数 Usage: mysql [OPTIONS] [database] //命令方式 -?, --help //显示帮助信息并退出 -I, --help //显示帮助信息并退出 --auto-rehash //自动补全功能,就像linux里面,按Tab键出提示差不多,下面有例子 -A, --no-auto-rehash //默认状态是没有自动补全功能的.-A就是不要自动补全功能 -B, --batch //ysql不使用历史文件,禁用交互 (Enables --silent

第5章4节《MonkeyRunner源码剖析》Monkey原理分析-启动运行: 命令行参数解析(原创)

天地会珠海分舵注:本来这一系列是准备出一本书的,详情请见早前博文"寻求合作伙伴编写<深入理解 MonkeyRunner>书籍".但因为诸多原因,没有如愿.所以这里把草稿分享出来,所以错误在所难免.有需要的就参考下吧,转发的话还请保留每篇文章结尾的出处等信息. 设置好Monkey的CLASSPATH环境变量以指定"/system/framework /framework/monkey.jar"后,/system/bin/monkey这个shell脚本就会通

命令行参数选项处理:getopt()及getopt_long()函数使用

在运行某个程序的时候,我们通常使用命令行参数来进行配置其行为.命令行选项和参数控制 UNIX 程序,告知它们如何动作.当 gcc的程序启动代码调用我们的入口函数 main(int argc,char *argv[]) 时,已经对命令行进行了处理.argc 参数包含程序参数的个数,而 argv 包含指向这些参数的指针数组. 程序的参数可以分为三种:选项,选项的关联值,非选项参数.例如: $gcc getopt_test.c -o testopt getopt_test.c是非选项参数,-o是选项,

MFC解析启动命令行参数——CCommandLineInfo类

MFC中CCommandLineInfo类被用于分析启动应用时的命令行参数. MFC应用一般都会在它的应用对象中使用函数InitInstance()创建这个类的一个本地实例.然后把该对象传给CWinApp::ParseCommandLine(),ParseCommandLine()又重复调用ParseParam()填充CCommandLineInfo对象.最后,CCommandLineInfo对象被传给CWinApp::ProcessShellCommand()来处理命令行参数和选项.所以我们会