【问题标题】:Golang flag: Ignore missing flag and parse multiple duplicate flagsGolang flag:忽略丢失的标志并解析多个重复的标志
【发布时间】:2022-02-10 16:04:04
【问题描述】:

我是 Golang 新手,我一直无法使用 flag 找到解决此问题的方法。

如何使用标志,以便我的程序可以处理这样的调用,其中 -term 标志可能出现的次数不定,包括 0 次:

./myprogram -f flag1
./myprogram -f flag1 -term t1 -term t2 -term  t3

【问题讨论】:

  • 将 -term 绑定到您自己类型的变量,实现 golang.org/pkg/flag/#Value 并收集每个 Set() 调用的值。
  • 投反对票的人能解释我的问题是什么吗?

标签: go go-flag


【解决方案1】:

你需要声明你自己的实现了 Value 接口的类型。这是一个例子。

// Created so that multiple inputs can be accecpted
type arrayFlags []string

func (i *arrayFlags) String() string {
    // change this, this is just can example to satisfy the interface
    return "my string representation"
}

func (i *arrayFlags) Set(value string) error {
    *i = append(*i, strings.TrimSpace(value))
    return nil
}

然后在你解析标志的主函数中

var myFlags arrayFlags

flag.Var(&myFlags, "term", "my terms")
flag.Parse()

现在所有术语都包含在切片myFlags

【讨论】:

  • 如果你的意思是,上面的工作和编译,那么是的。以上就是答案
  • 谢谢,你能告诉我这个解释是否正确吗?:由于 myFlags 被称为 Var 的 value 参数,所以 myFlags 变成了 Value 类型,并且 String() 和 Set() 方法会自动运行.不抱歉,我不小心发布了评论,然后因为过了 5 分钟而被锁定,无法编辑。
  • 是的,因为参数是Value类型,是一个接口。任何实现接口方法的结构都可以用来满足接口。有关接口的更多信息,请参见此处tour.golang.org/methods/10
【解决方案2】:

这个问题很有趣,可以有很多变化。

  • 数组
  • 地图
  • 结构

核心内容同@reticentroot answered

完成此接口的定义:Flag.Value

以下是示例,尽可能分享和提供相关链接

示例

预期用途:

type Books []string

func (*Books) String() string   { return "" }
func (*Books) Set(string) error { return nil }

type Dict map[string]string

func (*Dict) String() string   { return "" }
func (*Dict) Set(string) error { return nil }

type Person struct {
    Name string
    Age  int
}

func (*Person) String() string   { return "" }
func (*Person) Set(string) error { return nil }

func pseudocode() {
    flagSetTest := flag.NewFlagSet("test", flag.ContinueOnError)

    books := Books{}
    flagSetTest.Var(&books, "book", "-book C++ -book Go -book javascript")
    // expected output: books: []string{C++,Go,javascript}

    dict := Dict{}
    flagSetTest.Var(&dict, "dict", "-dict A:65|B:66")
    // expected output: dict: map[string]string{"A":"65", "B":"66"}

    // map
    person := Person{}
    flagSetTest.Var(&person, "person", "-person Name:foo|Age:18")
    // output: {Name:foo Age:18}

    flagSetTest.Parse(os.Args[1:])
    fmt.Println(person, books, dict)
}

完整代码

package main

import (
    "bufio"
    "errors"
    "flag"
    "fmt"
    "os"
    "reflect"
    "strconv"
    "strings"
)

type BooksValue []string

// https://github.com/golang/go/blob/2580d0e/src/flag/flag.go#L298
func (arr *BooksValue) String() string {
    /*
        value.String(): https://github.com/golang/go/blob/2580d0e/src/flag/flag.go#L870
        DefValue string:
            - https://github.com/golang/go/blob/2580d0e/src/flag/flag.go#L348
            - https://github.com/golang/go/blob/2580d0e/src/flag/flag.go#L914-L920
            - https://github.com/golang/go/blob/2580d0e/src/flag/flag.go#L529-L536
            - https://github.com/golang/go/blob/2580d0e/src/flag/flag.go#L464
    */
    return ""
}

// https://github.com/golang/go/blob/2580d0e/src/flag/flag.go#L299
func (arr *BooksValue) Set(value string) error {
    /*
        value: https://github.com/golang/go/blob/2580d0e/src/flag/flag.go#L947
        bool: Set(value): https://github.com/golang/go/blob/2580d0e/src/flag/flag.go#L966-L975
        else: Set(value): https://github.com/golang/go/blob/2580d0e/src/flag/flag.go#L986-L988
    */
    *arr = append(*arr, strings.TrimSpace(value))
    return nil
}

type DictValue map[string]string

func (m *DictValue) String() string {
    return ""
}

func (m *DictValue) Set(value string) error {
    arr := strings.Split(value, "|") // "key1:val1|key2:val2|..."
    for _, curPairStr := range arr {
        itemArr := strings.Split(curPairStr, ":")
        key := itemArr[0]
        val := itemArr[1]
        (*m)[key] = val
    }
    return nil
}

type PersonValue struct {
    Name     string
    Age      int
    Msg      string
    IsActive bool
}

func (s *PersonValue) String() string {
    return ""
}

func (s *PersonValue) Set(value string) error {
    arr := strings.Split(value, "|") // "Field1:Value1|F2:V2|...|FN:VN"

    for _, curPairStr := range arr {
        itemArr := strings.Split(curPairStr, ":")
        key := itemArr[0]
        val := itemArr[1]

        // [Access struct property by name](https://stackoverflow.com/a/66470232/9935654)
        pointToStruct := reflect.ValueOf(s)
        curStruct := pointToStruct.Elem()
        curField := curStruct.FieldByName(key)
        if !curField.IsValid() {
            return errors.New("not found")
        }

        // CanSet one of conditions: Name starts with a capital
        if !curField.CanSet() {
            return errors.New("can't set")
        }

        t := reflect.TypeOf(*s)
        structFieldXXX, isFound := t.FieldByName(key)
        if !isFound {
            return errors.New("not found")
        }

        switch structFieldXXX.Type.Name() {
        case "int":
            // https://github.com/golang/go/blob/2580d0e/src/flag/flag.go#L146-L153
            intValue, err := strconv.ParseInt(val, 0, strconv.IntSize)
            if err != nil {
                return errors.New("parse error: [int]")
            }
            curField.SetInt(intValue)
        case "bool":
            // https://github.com/golang/go/blob/2580d0e/src/flag/flag.go#L117-L121
            boolValue, err := strconv.ParseBool(val)
            if err != nil {
                return errors.New("parse error: [bool]")
            }
            curField.SetBool(boolValue)
        case "string":
            curField.SetString(val)
        default:
            return errors.New("not support type=" + structFieldXXX.Type.Name())
        }
    }

    return nil
}

func main() {

    flagSetTest := flag.NewFlagSet("test", flag.ContinueOnError)

    // array
    books := BooksValue{}
    flagSetTest.Var(&books, "book", "-book Go -book javascript ...")

    // map
    myMap := DictValue{}
    flagSetTest.Var(&myMap, "map", "-dict A:65|B:66")

    // struct
    person := PersonValue{Msg: "Hello world"}
    flagSetTest.Var(&person, "person", "-person Name:string|Age:int|Msg:string|IsActive:bool")

    testArgs := []string{"test",
        "-book", "Go", "-book", "javascript", // testArray
        "-map", "A:65|B:66|Name:Carson", // testMap
        "-person", "Name:Carson|Age:30|IsActive:true", // testStruct
    }

    testFunc := func(args []string, reset bool) {
        if reset {
            books = BooksValue{}
            myMap = DictValue{}
            person = PersonValue{}
        }

        if err := flagSetTest.Parse(args); err != nil {
            fmt.Printf(err.Error())
        }
        fmt.Printf("%+v\n", books)
        fmt.Printf("%+v\n", myMap)
        fmt.Printf("%+v\n", person)
    }

    testFunc(testArgs[1:], false)
    
    // ↓ play by yourself
    scanner := bufio.NewScanner(os.Stdin)
    for {
        fmt.Println("Enter CMD: ") // example: test -book item1 -book item2 -map key1:value1|key2:v2 -person Age:18|Name:Neil|IsActive:true
        scanner.Scan()             // Scans a line from Stdin(Console)
        text := scanner.Text()     // Holds the string that scanned
        args := strings.Split(text, " ")

        switch args[0] {
        case "quit":
            return
        case "test":
            testFunc(args[1:], true)
        }
    }
}

go playground

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2022-12-17
    • 2016-01-04
    • 2015-12-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-04-08
    相关资源
    最近更新 更多