флаг golang прекращает синтаксический анализ после первого не-опции

я создаю небольшой инструмент cli, который загружает мое приложение в разработке или производстве.

то, как я хочу, чтобы это работало, выглядит так:

app run --dev or app run --prod

Atm не анализирует флаги после моей команды, но только перед моей командой. Так что это работает

app --dev run or app --prod run

Любая идея, как исправить это, чтобы я мог использовать его после моей команды? вот мой код

func main() {
    //flag.Usage := usage
    flag.Parse()
    args := flag.Args()
    if len(args) == 0 {
        Usage()
        os.Exit(0)
    }

    if *dev {
        os.Setenv("ENV", "development")
    }

    if *prod {
        os.Setenv("ENV", "production")
    }

    switch {
    // Run
    case args[0] == "run" && len(args) == 1:
        os.Setenv("port", *port)
        log.Printf("Booting in %s", os.Getenv("ENV"))
        Run()

    // Help
    case args[0] == "help" && len(args) == 1:
        Usage()
    }
}

person Anthony De Meulemeester    schedule 04.08.2014    source источник
comment
Почему бы просто не вызвать ENV=dev yourapp run, чтобы избежать странного танца SetEnv, который у вас есть, и просто установить его напрямую (и только для этого запуска).   -  person elithrar    schedule 04.08.2014


Ответы (2)


Традиционно синтаксический анализатор параметров UNIX getopt() прекращает синтаксический анализ после первого не-параметра. Glibc изменил это поведение для поддержки опций в произвольных позициях, спорное решение. Пакет flag реализует традиционное поведение.

Одна вещь, которую вы можете сделать, это переставить массив аргументов перед разбором флагов. Вот что делает glibc:

func permutateArgs(args []string) int {
    args = args[1:]
    optind := 0

    for i := range args {
        if args[i][0] == '-' {
            tmp := args[i]
            args[i] = args[optind]
            args[optind] = tmp
            optind++
        }
    }

    return optind + 1
}

Этот код переставляет args таким образом, что параметры находятся впереди, оставляя имя программы нетронутым. permutateArgs возвращает индекс первого не-варианта после перестановки. Используйте этот код следующим образом:

optind := permutateArgs(os.Args)
flags.Parse()

// process non-options
for i := range os.Args[optind:] {
    // ...
}
person fuz    schedule 04.08.2014
comment
@AnthonyDeMeulemeester Повторите попытку. Теперь код должен работать. - person fuz; 04.08.2014
comment
это отличный ответ, хотя он не сохраняет порядок аргументов без флагов. например, учитывая аргументы a -x b c -y, вы получаете -x -y b c a вместо -x -y a b c. Решение, которое сохраняет порядок, было бы здорово! - person briceburg; 21.06.2017
comment
Также работает только для флагов Bool. Для флагов, которые принимают значения, такие как -o result.txt, этот метод будет рассматривать «result.txt» как аргумент, а не как флаг параметра. - person CodeExpress; 06.08.2017
comment
@CodeExpress Да, действительно. Не должно быть слишком сложно адаптировать код для рассмотрения других вариантов. - person fuz; 06.08.2017
comment
Спасибо. Вы также можете более легко обрабатывать не-опции с помощью flag.Args() - person Tillus; 20.06.2020

Это просто то, как пакет flag работает с аргументами. Обработка аргументов всегда несколько обусловлена ​​соглашением, и пакет flag определенно не соответствует стилю gnu, к которому многие привыкли.

Один из способов — отсортировать параметры перед командами:

// example argument sorting using sort.Stable
type ArgSlice []string

func (p ArgSlice) Len() int      { return len(p) }
func (p ArgSlice) Swap(i, j int) { p[i], p[j] = p[j], p[i] }

func (p ArgSlice) Less(i, j int) bool {
    if len(p[i]) == 0 {
        return false
    }
    if len(p[j]) == 0 {
        return true
    }
    return p[i][0] == '-' && p[j][0] != '-'
}

func main() {

    args := []string{"cmd", "-a", "arg", "-b", "-c"}
    sort.Stable(ArgSlice(args))
    // if sorting os.Args, make sure to omit the first argument
    // sort.Stable(ArgSlice(os.Args[1:]))

    fmt.Println(args)
}

Многие пакеты используют отдельные наборы флагов для подкоманд, что обеспечивает хорошее разделение, но это не сработает, если между подкомандами могут быть разные флаги. Единственное решение — дублировать флаги на каждом уровне.

Тем не менее, лучший ответ по-прежнему состоит в том, чтобы следовать соглашениям, используемым пакетом флагов, а не пытаться с ними бороться.

person JimB    schedule 04.08.2014