golang自带的命令行解析库flag库实践

1. 简介
flag用于解析命令行选项。有过类 Unix 系统使用经验的童鞋对命令行选项应该不陌生。例如命令ls -al列出当前目录下所有文件和目录的详细信息,其中-al就是命令行选项。

命令行选项在实际开发中很常用,特别是在写工具的时候。

指定配置文件的路径,如redis-server ./redis.conf以当前目录下的配置文件redis.conf启动 Redis 服务器;
自定义某些参数,如python -m SimpleHTTPServer 8080启动一个 HTTP 服务器,监听 8080 端口。如果不指定,则默认监听 8000 端口。
2. 快速使用
学习一个库的第一步当然是使用它。我们先看看flag库的基本使用:

package main

import (
  "fmt"
  "flag"
)

var (
  intflag int
  boolflag bool
  stringflag string
)

func init() {
  flag.IntVar(&intflag, "intflag", 0, "int flag value")
  flag.BoolVar(&boolflag, "boolflag", false, "bool flag value")
  flag.StringVar(&stringflag, "stringflag", "default", "string flag value")
}

func main() {
  flag.Parse()

  fmt.Println("int flag:", intflag)
  fmt.Println("bool flag:", boolflag)
  fmt.Println("string flag:", stringflag)
}
可以先编译程序,然后运行(我使用的是 MacOS):

$ go build -o flagDemo main.go
$ ./flagDemo -intflag 12 -boolflag 1 -stringflag test
输出:

int flag: 12
bool flag: true
string flag: test
如果不设置某个选项,相应变量会取默认值:

$ ./flagDemo -intflag 12 -boolflag 1
输出:

int flag: 12
bool flag: true
string flag: default
可以看到没有设置的选项stringflag为默认值default。

还可以直接使用go run,这个命令会先编译程序生成可执行文件,然后执行该文件,将命令行中的其它选项传给这个程序。

$ go run main.go -intflag 12 -boolflag 1
可以使用-h显示选项帮助信息:

$ ./flagDemo -h
Usage:
  -boolflag
        bool flag value
  -intflag int
        int flag value
  -stringflag string
        string flag value (default "default")
总结一下,使用flag库的一般步骤:

定义一些全局变量存储选项的值,如这里的intflag/boolflag/stringflag;
在init方法中使用flag.TypeVar方法定义选项,这里的Type可以为基本类型Int/Uint/Float64/Bool,还可以是时间间隔time.Duration。定义时传入变量的地址、选项名、默认值和帮助信息;
在main方法中调用flag.Parse从os.Args[1:]中解析选项。因为os.Args[0]为可执行程序路径,会被剔除。
注意点:

flag.Parse方法必须在所有选项都定义之后调用,且flag.Parse调用之后不能再定义选项。如果按照前面的步骤,基本不会出现问题。 因为init在所有代码之前执行,将选项定义都放在init中,main函数中执行flag.Parse时所有选项都已经定义了。
3. 选项格式
flag库支持多种命令行选项格式。

-flag
-flag=x
-flag x
--flag=x
--flag x
-和--都可以使用,它们的作用是一样的。有些库使用-表示短选项,--表示长选项。相对而言,flag使用起来更简单。

第一种形式只支持布尔类型的选项,出现即为true,不出现为默认值。 第三种形式不支持布尔类型的选项。因为这种形式的布尔选项在类 Unix 系统中可能会出现意想不到的行为。看下面的命令:

cmd -x *
其中,*是 shell 通配符。如果有名字为 0、false的文件,布尔选项-x将会取false。反之,布尔选项-x将会取true。而且这个选项消耗了一个参数。 如果要显示设置一个布尔选项为false,只能使用-flag=false这种形式。

遇到第一个非选项参数(即不是以-和--开头的)或终止符--,解析停止。运行下面程序:

$ ./flagDemo noflag -intflag 12
将会输出:

int flag: 0
bool flag: false
string flag: default
因为解析遇到noflag就停止了,后面的选项-intflag没有被解析到。所以所有选项都取的默认值。

运行下面的程序:

$ ./flagDemo -intflag 12 -- -boolflag=true
将会输出:

int flag: 12
bool flag: false
string flag: default
首先解析了选项intflag,设置其值为 12。遇到--后解析终止了,后面的--boolflag=true没有被解析到,所以boolflag选项取默认值false。

解析终止之后如果还有命令行参数,flag库会存储下来,通过flag.Args方法返回这些参数的切片。 可以通过flag.NArg方法获取未解析的参数数量,flag.Arg(i)访问位置i(从 0 开始)上的参数。 选项个数也可以通过调用flag.NFlag方法获取。

稍稍修改一下上面的程序:

func main() {
  flag.Parse()
    
  fmt.Println(flag.Args())
  fmt.Println("Non-Flag Argument Count:", flag.NArg())
  for i := 0; i < flag.NArg(); i++ {
    fmt.Printf("Argument %d: %s\n", i, flag.Arg(i))
  }
  
  fmt.Println("Flag Count:", flag.NFlag())
}
编译运行该程序:

$ go build -o flagDemo main.go
$ ./flagDemo -intflag 12 -- -stringflag test

输出:

[-stringflag test]
Non-Flag Argument Count: 2
Argument 0: -stringflag
Argument 1: test
解析遇到–终止后,剩余参数-stringflag test保存在flag中,可以通过Args/NArg/Arg等方法访问。

整数选项值可以接受 1234(十进制)、0664(八进制)和 0x1234(十六进制)的形式,并且可以是负数。实际上flag在内部使用strconv.ParseInt方法将字符串解析成int。 所以理论上,ParseInt接受的格式都可以。

布尔类型的选项值可以为:

取值为true的:1、t、T、true、TRUE、True;
取值为false的:0、f、F、false、FALSE、False。
4. 另一种定义选项的方式
上面我们介绍了使用flag.TypeVar定义选项,这种方式需要我们先定义变量,然后变量的地址。 还有一种方式,调用flag.Type(其中Type可以为Int/Uint/Bool/Float64/String/Duration等)会自动为我们分配变量,返回该变量的地址。用法与前一种方式类似:

package main

import (
  "fmt"
  "flag"
)

var (
  intflag *int
  boolflag *bool
  stringflag *string
)

func init() {
  intflag = flag.Int("intflag", 0, "int flag value")
  boolflag = flag.Bool("boolflag", false, "bool flag value")
  stringflag = flag.String("stringflag", "default", "string flag value")
}

func main() {
  flag.Parse()
    
  fmt.Println("int flag:", *intflag)
  fmt.Println("bool flag:", *boolflag)
  fmt.Println("string flag:", *stringflag)
}
编译并运行程序:

$ go build -o flagDemo main.go
$ ./flagDemo -intflag 12
将输出:

int flag: 12
bool flag: false
string flag: default

除了使用时需要解引用,其它与前一种方式基本相同。

5. 高级用法
5.1 定义短选项
flag库并没有显示支持短选项,但是可以通过给某个相同的变量设置不同的选项来实现。即两个选项共享同一个变量。 由于初始化顺序不确定,必须保证它们拥有相同的默认值。否则不传该选项时,行为是不确定的。

package main

import (
  "fmt"
  "flag"
)

var logLevel string

func init() {
  const (
    defaultLogLevel = "DEBUG"
    usage = "set log level value"
  )
  
  flag.StringVar(&logLevel, "log_type", defaultLogLevel, usage)
  flag.StringVar(&logLevel, "l", defaultLogLevel, usage + "(shorthand)")
}

func main() {
  flag.Parse()

  fmt.Println("log level:", logLevel)
}

编译、运行程序:

$ go build -o flagDemo main.go
$ ./flagDemo -log_type WARNING
$ ./flagDemo -l WARNING

使用长、短选项均输出:

log level: WARNING
不传入该选项,输出默认值:

$ ./flagDemo
log level: DEBUG

5.2 解析时间间隔
除了能使用基本类型作为选项,flag库还支持time.Duration类型,即时间间隔。时间间隔支持的格式非常之多,例如"300ms"、“-1.5h”、“2h45m"等等等等。 时间单位可以是 ns/us/ms/s/m/h/day 等。实际上flag内部会调用time.ParseDuration。具体支持的格式可以参见time(需fq)库的文档。

package main

import (
  "flag"
  "fmt"
  "time"
)

var (
  period time.Duration
)

func init() {
  flag.DurationVar(&period, "period", 1*time.Second, "sleep period")
}

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

根据传入的命令行选项period,程序睡眠相应的时间,默认 1 秒。编译、运行程序:

$ go build -o flagDemo main.go
$ ./flagDemo
Sleeping for 1s...

$ ./flagDemo -period 1m30s
Sleeping for 1m30s...

5.3 自定义选项
除了使用flag库提供的选项类型,我们还可以自定义选项类型。我们分析一下标准库中提供的案例:

package main

import (
  "errors"
  "flag"
  "fmt"
  "strings"
  "time"
)

type interval []time.Duration

func (i *interval) String() string {
  return fmt.Sprint(*i)
}

func (i *interval) Set(value string) error {
  if len(*i) > 0 {
    return errors.New("interval flag already set")
  }
  for _, dt := range strings.Split(value, ",") {
    duration, err := time.ParseDuration(dt)
    if err != nil {
      return err
    }
    *i = append(*i, duration)
  }
  return nil
}

var (
  intervalFlag interval
)

func init() {
  flag.Var(&intervalFlag, "deltaT", "comma-seperated list of intervals to use between events")
}

func main() {
  flag.Parse()

  fmt.Println(intervalFlag)
}
首先定义一个新类型,这里定义类型interval。

新类型必须实现flag.Value接口:

// src/flag/flag.go
type Value interface {
  String() string
  Set(string) error
}

其中String方法格式化该类型的值,flag.Parse方法在执行时遇到自定义类型的选项会将选项值作为参数调用该类型变量的Set方法。 这里将以,分隔的时间间隔解析出来存入一个切片中。

自定义类型选项的定义必须使用flag.Var方法。

编译、执行程序:

$ go build -o flagDemo main.go
$ ./flagDemo -deltaT 30s
[30s]
$ ./flagDemo -deltaT 30s,1m,1m30s
[30s 1m0s 1m30s]
如果指定的选项值非法,Set方法返回一个error类型的值,Parse执行终止,打印错误和使用帮助。

$ ./flagDemo -deltaT 30x
invalid value "30x" for flag -deltaT: time: unknown unit x in duration 30x
Usage of D:\code\golang\src\github.com\darjun\go-daily-lib\flag\self-defined\main.exe:
  -deltaT value
        comma-seperated list of intervals to use between events
5.4 解析程序中的字符串
有时候选项并不是通过命令行传递的。例如,从配置表中读取或程序生成的。这时候可以使用flag.FlagSet结构的相关方法来解析这些选项。

实际上,我们前面调用的flag库的方法,都会间接调用FlagSet结构的方法。flag库中定义了一个FlagSet类型的全局变量CommandLine专门用于解析命令行选项。 前面调用的flag库的方法只是为了提供便利,它们内部都是调用的CommandLine的相应方法:

// src/flag/flag.go
var CommandLine = NewFlagSet(os.Args[0], ExitOnError)

func Parse() {
  CommandLine.Parse(os.Args[1:])
}

func IntVar(p *int, name string, value int, usage string) {
  CommandLine.Var(newIntValue(value, p), name, usage)
}

func Int(name string, value int, usage string) *int {
  return CommandLine.Int(name, value, usage)
}

func NFlag() int { return len(CommandLine.actual) }

func Arg(i int) string {
  return CommandLine.Arg(i)
}

func NArg() int { return len(CommandLine.args) }
同样的,我们也可以自己创建FlagSet类型变量来解析选项。

package main

import (
  "flag"
  "fmt"
)

func main() {
  args := []string{"-intflag", "12", "-stringflag", "test"}

  var intflag int
  var boolflag bool
  var stringflag string

  fs := flag.NewFlagSet("MyFlagSet", flag.ContinueOnError)
  fs.IntVar(&intflag, "intflag", 0, "int flag value")
  fs.BoolVar(&boolflag, "boolflag", false, "bool flag value")
  fs.StringVar(&stringflag, "stringflag", "default", "string flag value")

  fs.Parse(args)
  
  fmt.Println("int flag:", intflag)
  fmt.Println("bool flag:", boolflag)
  fmt.Println("string flag:", stringflag)
}
NewFlagSet方法有两个参数,第一个参数是程序名称,输出帮助或出错时会显示该信息。第二个参数是解析出错时如何处理,有几个选项:

ContinueOnError:发生错误后继续解析,CommandLine就是使用这个选项;
ExitOnError:出错时调用os.Exit(2)退出程序;
PanicOnError:出错时产生 panic。
随便看一眼flag库中的相关代码:

// src/flag/flag.go
func (f *FlagSet) Parse(arguments []string) error {
  f.parsed = true
  f.args = arguments
  for {
    seen, err := f.parseOne()
    if seen {
      continue
    }
    if err == nil {
      break
    }
    switch f.errorHandling {
    case ContinueOnError:
      return err
    case ExitOnError:
      os.Exit(2)
    case PanicOnError:
      panic(err)
    }
  }
  return nil
}

与直接使用flag库的方法有一点不同,FlagSet调用Parse方法时需要显示传入字符串切片作为参数。因为flag.Parse在内部调用了CommandLine.Parse(os.Args[1:])。

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/19491.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

爆改vue3 setup naiveui可编辑table

使用naiveui官网的可编辑table总是报错&#xff0c;所以手写了一个 思路&#xff1a;table数据数组unitMsgArr对应一个布尔的数组isEditArr &#xff0c;点击table可编辑的行数据的时候&#xff0c;更改对应的isEdit为true&#xff0c;此时渲染组件EditCom&#xff0c;在EditC…

flask---》登录认证装饰器/配置文件/路由系统

登录认证装饰器 # 0 装饰器的本质原理-# 类装饰器&#xff1a;1 装饰类的装饰器 2 类作为装饰器 # 1 装饰器使用位置&#xff0c;顺序 # 3 flask路由下加装饰器&#xff0c;一定要加endpoint-如果不指定endpoint&#xff0c;反向解析的名字都是函数名&#xff0c;不加装饰器…

【数据可视化】(一)数据可视化概述

目录 0.本章节概述 一、数据可视化 1、什么是数据可视化? 2、数据可视化的好处 3、数据可视化的用途 二、数据探索 1、数据相关工具的使用情景: 2、探索性查询 三、数据挑战 1、什么是数据挑战?

24考研数据结构-数组和特殊矩阵

目录 数据结构&#xff1a;数组与特殊矩阵数组数组的特点数组的用途 特殊矩阵对角矩阵上三角矩阵和下三角矩阵稀疏矩阵特殊矩阵的用途 结论 3.4 数组和特殊矩阵3.4.1数组的存储结构3.4.2普通矩阵的存储3.4.3特殊矩阵的存储1. 对称矩阵(方阵)2. 三角矩阵(方阵)3. 三对角矩阵(方阵…

图像处理之hough圆形检测

hough检测原理 点击图像处理之Hough变换检测直线查看 下面直接描述检测圆形的方法 基于Hough变换的圆形检测方法 对于一个半径为 r r r&#xff0c;圆心为 &#xff08; a , b &#xff09; &#xff08;a,b&#xff09; &#xff08;a,b&#xff09;的圆&#xff0c;我们将…

vscode里安装Go插件和配置Go环境

vscode是一款跨平台、轻量级、插件多的开源IDE&#xff0c;在vscode不仅可以配置C/C、Python、R、Ruby等语言的环境&#xff0c;还可以配置Go语言的环境。这里介绍在vscode里安装Go语言的插件和配置Go语言环境&#xff0c;系统是Win10 64位。 1、下载Go安装包和配置GOROOT、GO…

283. 移动零

移动零 题目 给定一个数组 nums&#xff0c;编写一个函数将所有 0 移动到数组的末尾&#xff0c;同时保持非零元素的相对顺序。 请注意 &#xff0c;必须在不复制数组的情况下原地对数组进行操作。 答案1 class Solution { public:void moveZeroes(vector<int>& nums…

深度思考java基础面经

基础 1 重载和重写的区别 1.1 区别 重载和重写是两个编程术语&#xff0c;主要用在面向对象的编程语言中。以下是重载和重写的主要区别&#xff1a; 方法重载&#xff08;Overloading&#xff09;&#xff1a;发生在同一个类中&#xff0c;方法重载是指一个类中定义了多个同…

内部类和类的初始化实例化过程

一、内部类 1. 什么是内部类 在类的内部定义一个类&#xff0c;专门服务于外部类&#xff0c;便于访问外部类的属性和方法。 2. 内部类的分类 成员内部类&#xff1a;定义在类的成员变量的位置 静态内部类&#xff1a;使用起来跟普通的外部类基本一致&#xff1b;非静态内部…

一年级数学 数一数(一到十)

今天我们来学习数一数 有一些老人 眼睛可能花了 需要我们在动物园数清楚是多少个动物 然后告诉他们 可能有的小朋友 不知道某些数字怎么读 您可以打开地址 https://fanyi.baidu.com/?aldtype16047#zh/en/ 将数字 输入到 输入框内 然后点击 下面的小话筒 系统就会读出来了 小…

音视频知识:MPEG-4、H264、MP4、AAC之间的关系

MPEG-4 一种编码标准。是国际标准化组织 (ISO) 主要针对消费类应用&#xff0c;已经针对运动图像压缩定义的标准。MPEG&#xff08;Moving Picture Experts Group&#xff09;标准包括 MPEG1、MPEG2与 MPEG4。 MPEG-4标准目前分为27个部分&#xff0c;统称为ISO/IEC14496国际标…

高忆管理:多重利好共振 外资加码布局A股

资本商场活泼信号正在继续开释&#xff0c;内外资决心取得有力提振。以北向资金为代表的外资近来表现活泼&#xff0c;近六个买卖日已连续净买入超500亿元。多家外资组织近期表态称&#xff0c;伴跟着方针力度加强&#xff0c;我国经济有望继续复苏&#xff0c;活泼看好我国权益…

2023年8月美团外卖3-18元红包优惠券天天领取活动日历及美团外卖红包领取使用

2023年8月美团外卖3-18元红包天天领取活动日历 根据上图美团外卖红包领取活动时间表以下时间可以天天领取3-18元美团外卖红包优惠券&#xff1a; 1、2023年8月18日 可领取美团外卖18元神券节红包&#xff1b; 2、2023年8月每周六、周日每天可领取12元美团外卖节红包&#xff…

【C++】类和对象-继承

0.前言 1.基本语法 继承的用处就是极大的减少代码的重复性&#xff0c;如果没有用继承&#xff0c;看看以下代码&#xff0c;你知道了。。。。 基本实现代码&#xff1a; #include <iostream> using namespace std; /******************************************/void …

万界星空/推出生产制造执行MES系统/开源MES/免费下载

免费MES系统介绍 什么是MES系统呢&#xff1f;MES系统主要功能就是解决“如何生产”的问题。通过实施MES系统&#xff0c;一站式解决您所困扰的所有生产制作流程问题。 普通的免费MES系统只提供简单的基本功能让客户体验&#xff0c;而万界星空MES系统运用低代码的形式开发&a…

【二开】JeecgBoot-vue3二次开发 前端 扩展online表单js增强等-初始化列表之后执行

【二开】JeecgBoot-vue3二次开发 前端 扩展online表单js增强等-初始化列表之后执行 二开位置 OnlineAutoList.js.initAutoList 定义方法 /*** 初始化列表之后执行* js增强* param tableColumns* returns {Promise<void>|*}*/onlineTableContext["afterInitAutoList…

Codeforces Round 855 (Div. 3) E题题解

文章目录 [ Unforgivable Curse (hard version)](https://codeforces.com/contest/1800/problem/E2)问题建模问题分析方法1分析性质1.分析操作对元素位置的影响2.分析可以使用操作的元素可以与相邻元素交换位置的作用代码 方法2通过DFS得到相互可以交换位置的字符集合代码 方法…

Spring Boot的自动配置原理

一.原理解释 Spring Boot的自动配置是Spring框架的一个重要特性&#xff0c;它旨在简化应用程序的开发和部署过程。自动配置通过基于类路径中的依赖关系和配置文件内容来预先配置Spring应用程序的各种组件和功能。这样&#xff0c;我们可以在无需显式配置大量参数的情况下&…

干翻Dubbo系列第六篇:SpringBoot与Dubbo整合

文章说明 本文内容整理自《孙哥说Dubbo系列视频课程》&#xff0c;孙帅老师课程细致、全面、深入、性价比极高。B站搜孙帅suns可以找到对应的试听视频&#xff0c;或者直接添加老师微信号suns45与他直接联系 一&#xff1a;SpringBoot应用Dubbo开发应用的核心思路分析 深度封…

消息中间件应用场景介绍

提高系统性能首先考虑的是数据库的优化&#xff0c;但是数据库因为历史原因&#xff0c;横向扩展是一件非常复杂的工程&#xff0c;所有我们一般会尽量把流量都挡在数据库之前。 不管是无限的横向扩展服务器&#xff0c;还是纵向阻隔到达数据库的流量&#xff0c;都是这个思路。…