web-dev-qa-db-fra.com

Vérifier si le drapeau a été fourni dans Go

Avec le paquetage indicateur, existe-t-il un bon moyen de distinguer si un indicateur de chaîne a été passé?

Par exemple, lorsque l'indicateur n'est pas passé, je souhaite le définir sur une valeur dynamique par défaut. Cependant, je veux le mettre à vide si l'indicateur a été fourni mais avec une valeur de "".

Actuel je fais ce qui suit:

flagHost = flag.String(flagHostFlagKey, "", "...")
...
setHostname := false
for _, arg := range os.Args {
    if arg == "-"+flagHostFlagKey {
        setHostname = true
    }
}

if !setHostname {
     ...

Ce qui semble bien fonctionner, mais est un peu moche. Y at-il une meilleure façon de rester avec le paquet drapeau standard?

19
Kyle Brandt

Utilisez la fonction flag.Visit():

func isFlagPassed(name string) bool {
    found := false
    flag.Visit(func(f *flag.Flag) {
        if f.Name == name {
            found = true
        }
    })
    return found
}
2
Markus Heukelom

Les types d'indicateurs intégrés ne prennent pas en charge la distinction des valeurs par défaut et l'affectation explicite à la valeur par défaut. Cependant, le paquetage flag est assez flexible et vous permet de lancer votre propre type à l'aide de l'interface flag.Value.

Voici un exemple complet contenant un indicateur de chaîne qui enregistre si elle a été affectée.

package main

import (
    "flag"
    "fmt"
)

type stringFlag struct {
    set   bool
    value string
}

func (sf *stringFlag) Set(x string) error {
    sf.value = x
    sf.set = true
    return nil
}

func (sf *stringFlag) String() string {
    return sf.value
}

var filename stringFlag

func init() {
    flag.Var(&filename, "filename", "the filename")
}

func main() {
    flag.Parse()
    if !filename.set {
        fmt.Println("--filename not set")
    } else {
        fmt.Printf("--filename set to %q\n", filename.value)
    }
}

Voici quelques exemples de courses:

$ go run a.go -filename=abc
--filename set to "abc"

$ go run a.go -filename=
--filename set to ""

$ go run a.go
--filename not set
22
Paul Hankin

Le problème avec l'utilisation d'un type d'indicateur personnalisé (l'exemple stringFlag dans ce fil de discussion) est que vous êtes légèrement contrarié par la sortie PrintDefaults (c'est-à-dire --help). Par exemple, avec un indicateur de nom d'utilisateur de chaîne et un indicateur de nom de serveur de chaîne de caractères stringFlag, --help ressemble à ceci:

-server value
    server:port (default localhost:1234)
-username string
    username (default "kimmi")

Notez que ce sont deux arguments de chaîne pour l'utilisateur, mais présentés différemment, car stringFlag n'est pas une chaîne.

flag 'Flagset a une carte interne qui inclut les drapeaux déclarés (' formels ') et ceux effectivement définis (' réels '). Le premier est disponible via Lookup (), bien que hélas le dernier ne soit pas exposé, ou vous pouvez simplement écrire:

var servername = flag.String("server", "localhost:8129", "server:port")

flag.Parse()

if f := flag.CommandLine.LookupActual("server"); f != nil {
    fmt.Printf("server set to %#v\n", f)
} else {
    fmt.Printf("server not set\n")
}

Il semble que le mieux que vous puissiez faire, si vous voulez une sortie cohérente avec PrintDefaults (), consiste à utiliser Visit pour extraire votre propre vue de «réelle» (VisitAll fait la même chose avec «formal»):

var servername = flag.String("server", "localhost:8129", "server:port")

flag.Parse()

flagset := make(map[string]bool)
flag.Visit(func(f *flag.Flag) { flagset[f.Name]=true } )

if flagset["server"] {
    fmt.Printf("server set via flags\n")
} else {
    fmt.Printf("server not explicitly set, using default\n")
}
13
Ben L

Pour utiliser une valeur dynamique par défaut pour un indicateur, créez-le avec le paramètre par défaut défini sur la valeur dynamique:

func main() {
  flagHost = flag.String(flagHostFlagKey, computedHostFlag(), "...")
  flag.Parse()
  // *flagHost equals the return value from computedHostFlag() if 
  // the flag is not specified on the command line.
  ...
}

Avec cette approche, il n'est pas nécessaire de détecter si l'indicateur est spécifié sur la ligne de commande. En outre, l’aide indique la valeur par défaut correcte.

Si la valeur calculée dépend d'autres indicateurs ou est coûteuse à calculer, utilisez l'approche proposée dans l'une des autres réponses.

3
ThunderCat

Faites face au même problème, mais avez même un cas complexe avec bool flag, dans ce cas, computedHostFlag () ne fonctionne pas, car vous pouvez fournir à la création de drapeau uniquement true ou false. La solution "type stringFlag struct" n'est pas non plus la meilleure, car elle gâche l'idée des valeurs par défaut.

Résolvez-le de cette façon: créez deux ensembles d'indicateurs, avec des valeurs par défaut différentes, après analyse - il suffit de cocher - si les indicateurs du premier ensemble ont la même valeur que ceux du deuxième ensemble - cela signifie que la valeur d'indicateur a été fournie par l'utilisateur à partir de la commande ligne. S'ils diffèrent, cela signifie que cet indicateur a été défini par défaut.

package main

import (
    "fmt"
    "flag"
)

func main() {
    args := []string{"-foo="}

    flagSet1 := flag.NewFlagSet("flagSet1", flag.ContinueOnError)
    foo1 := flagSet1.String("foo", "-", ``)
    boolFoo1 := flagSet1.Bool("boolfoo", false, ``)
    flagSet1.Parse(args)

    flagSet2 := flag.NewFlagSet("flagSet2", flag.ContinueOnError)
    foo2 := flagSet2.String("foo", "+", ``)
    boolFoo2 := flagSet2.Bool("boolfoo", true, ``)
    flagSet2.Parse(args)

    if *foo1 != *foo2 {
        fmt.Println("foo flag set by default")
    } else {
        fmt.Println("foo flag provided by user")
    }

    if *boolFoo1 != *boolFoo2 {
        fmt.Println("boolfoo flag set by default")
    } else {
        fmt.Println("boolfoo flag provided by user")
    }
}

playground: https://play.golang.org/p/BVceE_pN5PO , pour une exécution réelle de la CLI, vous pouvez procéder de la manière suivante: https://play.golang.org/p/WNvDaaPj585

1
ewgra

Je pense qu'un moyen plus fiable consiste à vérifier si un indicateur dans les paramètres de ligne de commande (os.Args [1:]) est préfixé par "préfixe" + str.

func isInSlice(str string, list []string, prefix string) bool {
    for _, v := range list {
        if strings.HasPrefix(v, prefix + str) {
            return true
        }
    }
    return false
}
0
zhayu