Skip to content(if available)orjump to list(if available)

argp: GNU-style command line argument parser for Go

acmj

In general, command-line argument parsers should just follow the GNU style. No more, no less. Deviations confuse users as it is not immediately obvious to them what rules a parser is imposing.

> options can have multiple values: -a 1 2 3 means that a is an array/slice/struct of three numbers of value [1,2,3]

Allowing multiple values is inconsistent because you can't tell in "./cmd -a 1 2 3" whether 2 and 3 are positional arguments or arguments for -a. This is not a GNU style. The GNU way is "./cmd -a 1 -a 2 -a 3" (or "./cmd -a 1,2,3"). This package supports that, which is good.

> option values can be separated by a space, equal sign, or nothing: -a1 -a=1 -a 1 are all equal

"--a=1" is a GNU style but "-a=1" is not. This is a minor issue, though.

Also, does this package support "--"? Everything following "--" should be treated as positional arguments.

nloomans

I disagree with it being a minor issue. If I write a shell script around a program that accepts GNU-style arguments, I expect the following to be correct:

    ./cmd -a"$USER_CONTROLLED_DATA"
A program using this package would break that assumption, introducing a bug where this user-controlled data cannot start with an '='.

duckerude

I researched this for my own argument parser (https://github.com/blyxxyz/lexopt/issues/13) and concluded that it's a minor issue.

This syntax is supported by argparse and clap, the most popular argument parsers for Python and Rust respectively, and it seems to have caused almost no problems for them. It's a problem for the uutils implementation of cut, since `cut -d=` is common, but that's the only instance I could find after a long time scouring search engines and bug trackers and asking for examples.

If anyone does know of other examples or other places this has been discussed I'd love to hear it though, maybe I just haven't found them.

(Also, the more reliable way to write this in general is `-a "$USER_CONTROLLED_DATA"`, since that'll behave correctly if $USER_CONTROLLED_DATA is empty. As will `-a="$USER_CONTROLLED_DATA"` if you know the command supports it.)

edoceo

In the Gentoo world, sometimes you need to give an exact package name which looks like `=net-misc/foo-0.1.2-r1`. The exact match has to start with the '='.

ucarion

I think short options taking a value in the same argv (i.e. `-o=1` stuff) isn't a GNUism mostly because it's backwards-incompatible with POSIX. `=` is a valid getopt option character, `chmod` uses it.

That said, I think? 'nloomans means for USER_CONTROLLED_DATA to be a set of short flags, not flag values, as in:

    root@08e9950d5bfd:/# export USER_CONTROLLED_DATA=lh 
    root@08e9950d5bfd:/# ls -a"$USER_CONTROLLED_DATA"
    total 56K
    drwxr-xr-x   1 root root 4.0K Mar 23 16:51 .
    drwxr-xr-x   1 root root 4.0K Mar 23 16:51 ..
    [...]
Not that I've seen this in the wild before. But everyone's use of bash is a unique personal hell anyway.

cb321

Ha! I just said that. :-)

Anyway, one other alternative for the `cut` situation is to allow either ':' or '=' to optionally separate the key and the value. Then you can say `cut -d:=` or `cut -d=:` if you wanted to use either one. This is what https://github.com/c-blake/cligen does (for Nim, not Go).

fragmede

I'm sad docopt (http://docopt.org/ ) didn't get more uptake because I'm a fan of self-documenting code.

cb321

It is safer to just put in the space (much like you put in the quotes to be safe). Python's argparse will also accept but not require an `=` separator (maybe optparse, too - I haven't checked that one).

mort96

It's only "safer" because argp has this particular bug. It's safer for argp (or python's argument parser, for that matter) to not have surprising buggy features like this.

Arch-TK

Unfortunately a lot of people nowadays don't seem to notice or care and you even get weird arguments like the one about "Go style" vs "GNU style" below. I've also heard arguments about making it more "user friendly" (at the cost of any power user wanting to re-write your tool).

I wouldn't mind if GNU style was consistently extended to allow annotated required arguments. But that's about it.

That being said. As a rule, if your command line utility has enough positional arguments that you're forgetting which one is which, it's a badly designed command line interface. Most often it's because you're doing too much, not settling on sensible defaults for things which you shouldn't need to specify every time, or just doing something outright weird.

mjevans

IME commands get there by expressing complexity that belongs in a full configuration file on the command line (as well / instead).

It's nice if things are simple enough that a hand-full or two of flags are sufficient. However more complex programs that do more or substantially different things from traditional bytestream filter programs often do need proper configuration files.

null

[deleted]

ants_everywhere

Are you saying this because you think the GNU standard is particularly well thought out, or because it's so common that deviations are confusing in general?

I'm so used to the GNU conventions that I'm not really aware of what the alternatives are or what their merits are.

acmj

The most important part is to have a standard, which doesn't need to be perfect. On argument parsing, I actually think the GNU way is the best so far. I have seen various deviations from GNU and I personally regard all of them inferior.

null

[deleted]

gjvc

GNU became the de-facto standard at least 20 years ago

cratermoon

Disagree. Go's style may not be GNU style, but it's consistent in the Go community. GNU style is not All That and a Bag of Chips.

arp242

> it's consistent in the Go community.

Except it's not? There are tons of flag packages in real use, because the stdlib one kind of sucks. Even the "go" tool itself works around some of its limitations (things like "go test ./... -v" won't work out of the box, since it will stop parsing flags at the first non-flag, so the go command reorders os.Args before sending it off to the flag package).

Things like "-a 1 2 3" are not standard "Go style" at all. I've never seen that.

thiht

> because the stdlib one kind of sucks

It doesn’t. It’s simpler, which makes it not suck in my books.

All the stuff about short vs long, grouping short flags together, allowing the last flag of a group of short flags to take an argument, allowing a flag to take multiple values, etc. is cool, but confusing and quite hard to do right.

In Go it’s just -flag[=value].

treyd

You can't expect the user to remember upfront what language the tool they're using was written in and context switch the style they're expressing their intents in based on that. The Go community has consistently shown it's not very good at making well-thought out decisions. GNU is obviously the better approach here.

kirici

> The Go community has consistently shown it's not very good at making well-thought out decisions.

Please, do name one language that has.

hnlmorg

As a heavy user of Go tooling, I still really dislike Gos style.

bborud

Agree. It was an unnecessary thing to do.

acmj

When I use a command-line tool, I don't know and don't care if it is written in Go or not. I just want to use it the same way as most of the other traditional Unix tools. The Go style gets in the way. We would have much more consistent CLI between tools if Go had just followed the GNU style at the beginning. The same can be said to many other languages like Nim that want to reinvent command-line argument parsing with their standard libraries. https://xkcd.com/927/ came to mind.

klodolph

Can’t you just use --long options everywhere?

For, like, 95% of the tools out there, I’m going to use --long every single time.

Anyway, I don’t even know the options that a program takes until I read the program docs. The program docs will tell me that the option is -host=localhost or --host=localhost or --bind-addr=localhost:8000.

The other 5% are tools like ls, cp, mv. As far as I care, ancient tools are the only ones permitted to have short options that combine into a single option, like old-school getopt. Maybe a few exceptions now and then.

marxisttemp

No, command tools should follow the POSIX standards, not GNU.

greyw

POSIX doesnt even define long opts. I conciously dont follow POSIX these days. The standard will adapt once enough pressure has built up as they mostly document existing things instead of innovate. So I need to apply pressure.

masklinn

You're almost right. No command tools should follow the POSIX standard. A standard which all but forbids long options deserves only scorn.

NekkoDroid

POSIX doesn't even provide any utilities to write long opts and it also doesn't even define any long opts for basically any of its commands, so by default it is barely usable in maintainable scripts.

latchkey

Cobra https://github.com/spf13/cobra uses https://github.com/spf13/pflag, which supports GNU style flags. Cobra has been developed for many years now and has a ton of additional features, like automatically generating the autocompletions and has tons of unit tests.

I'd just use cobra.

imiric

Cobra is certainly popular, but it has many weird edge cases and limitations. It's been a while since I've used it so I can't recall the specifics, but I do remember it being a very poor experience, both as a developer and as a user.

I've had a much better experience with https://github.com/alecthomas/kong , and I've heard good things about https://github.com/urfave/cli , so I would recommend those over Cobra.

networked

I have used both argp and Kong. I consider them two of the best Go CLI libraries for different reasons. Kong has a full set of advanced functionality like flag groups and associating environment variables with options. It is highly useful in a larger project with a complex CLI. For a simple command-line interface, I prefer argp's small API and defaults, like exit with status 2 on bad usage. (Disclosure: I contributed this default to argp.)

Kong 1.7 has a disadvantage when it comes to negative numbers. argp parses `foo --bar -1` as `foo --bar=-1` the way you expect when `--bar` takes a value, but Kong treats `-1` as a flag.

Edit: Expanded the first paragraph.

acmj

> argp parses `foo --bar -1` as `foo --bar=-1` the way you expect when `--bar` takes an value, but Kong treats `-1` as a flag.

This seems a bug in Kong. Has someone reported it?

peterldowns

They all have their warts, but "just pick cobra/kong/cli" is what I go with these days. I'm still partial to Cobra because of the nice autocompletion scripts it generates for me so I get tab-completion for free.

MrDarcy

This is the correct answer. Just use Cobra, the automatic autocomplete is the best UX for users.

I’ve also tried all of them. cobra is more than good enough and what issues remain in 2025 are easily and reliably worked around to be non issues.

donatj

Yes! People love Cobra and yet every time I've used it, it's felt super janky and unreliable at actually doing what you think it should.

Not a fan.

latchkey

Kong doesn't explicitly say it supports GNU-style, which is why I didn't mention it.

hujun

one thing I liked about cobra is the auto complete support, but I don't like the manual process of define command and parameter and not easy to extend, so I wrote https://github.com/hujun-open/myflags, it is built-on top of cobra but allow uses a struct to define all commands/args and also easily to extend support new types (even existing types)

programd

I’ll second that. Cobra is the industrial strength flag parsing package most of the professional Go ecosystem has settled on. It’s the boring choice in the best sense of the word. Use it and move on with life.

CamouflagedKiwi

I've become a fan of https://github.com/jessevdk/go-flags (also enjoyed structopt which is similar for Rust). I much prefer defining the flags statically on a type which stores them after rather than building it all up programmatically.

Otherwise, I'm not really sure what this offers that alternatives don't, apart from the -a 1 2 3 -> [1,2,3] business which seems highly confusing and undesirable to me so I'd think of it as more of an anti-feature.

mway

Also a fan of go-flags, I use it by default most of the time (unless the use case is so basic that the stdlib flags usage would be sufficiently simple).

I think my only real point of "frustration" (mild at best) is that flag composability isn't always possible (eg embedding/sharing common non-root flags across flag structs), IIRC struct tag eval doesn't reach nested types.

Other than that though, it's one of those nice libraries that just does a thing and gets out of your way.

CamouflagedKiwi

Hmmmm I thought that sharing a common type did work in the way you would expect it to? I haven't tried embedding with it though I don't think.

TBH I rarely do it much anyway, mostly the flag struct ends up being one anonymous struct which is operated on in the main package only. I do like writing custom types for parsing though - I've seen engineers do a bunch of post-hoc parsing logic which generally just ends up being messy.

cedws

Struct tags = reflection, and reflection is nasty. They also aren't validated at compile time. Liberal use like this should be avoided.

CamouflagedKiwi

Do you also avoid using packages like encoding/js, which make equally liberal use of struct tags and reflection?

cedws

encoding/json does not make liberal use of struct tags, it uses only one. It's also battle tested and lives in the standard library. I don't have faith in many other projects to get bug-prone reflection code right.

quacker

In what way is reflection nasty?

What compile-time validation do I actually lose by using struct tags for CLI arg parsing?

cedws

Reflection is slow and bug prone. Struct tags are widely considered in the Go community to be a nasty hack yet a necessary evil. They should be used at little as possible because if you mess them up you don't get a compile error, you get a runtime error.

edoceo

I use Kong https://github.com/alecthomas/kong

I does everything, including too complicated options.

odc

The standard flag package in Go also supports GNU style flags.

https://pkg.go.dev/flag#hdr-Command_line_flag_syntax

sandreas

Interesting. I used

https://github.com/urfave/cli

in the past and was pretty happy with it. However sometimes it's good to try something New :-)

dotemacs

I'm glad that this package exists.

I know of UNIX parameters, with a single hyphen & a single letter, and GNU parameters, with a double parameter and a word. But a single hyphen and then a word: *mind broken*.

When I first saw that on Terraform, I was upset & conflicted.

OK, I know that Java also has mental parameter options like that ('-version', note the *single* hyphen followed by, gasp, a word!), but I just dismissed it as weird enterprise-ism.

Then I saw that this 'single hyphen + word' was an accepted convention for parameters, in the Go world & I was disappointed.

Glad that this package is fighting the good fight.

darrenf

> OK, I know that Java also has mental parameter options like that ('-version', note the *single* hyphen followed by, gasp, a word!), but I just dismissed it as weird enterprise-ism.

I think "mental" is being too kind about java.

    $ java -version
and

    $ java --version
both work, their outputs are different, and `-version` goes to STDERR whereas `--version` goes to STDOUT.

Spivak

I'm gonna take a stab here and say that -version behaves in a BSD manner and --version behaves in a GNU manner.

guappa

It's just that if you pass "ersion" to --verbose it prints the version :D

PhilipRoman

`find` is also notorious for its parameter style. But in my opinion programmers sometimes put too much importance on following conventions. You're not gonna go blind from looking at an incorrectly indented line. If the founding fathers of unix didn't want us creating horrifying monstrosities, they shouldn't have given us unlimited access to argv :)

bqmjjx0kac

Fun fact, `find`'s wonky argument syntax predates GNU syntax. It was part of Version 5 Unix from 1974. https://en.wikipedia.org/wiki/Find_(Unix)

petepete

I used find frequently for more than a decade and it just never felt natural to me. I get there in the end but have to stop and think.

With fd, a modern 'equivalent', I barely think just type.

Same for grep.

orthoxerox

As was `dd`, another program with an unorthodox syntax.

null

[deleted]

typ

Maybe the README should mention whether reflection (the part that would disable dead-code elimnation) is used? That was a pain point for me when I had to investigate a binary bloat issue due to reflect.

null

[deleted]

reactordev

Aside from the other comments, an ick I have is that struct tags are being abused here. Instead of providing a grouping like json, yaml, mapstructure, gorm, etc - it just goes willy nilly with struct tags like it owns the whole space. I’d like to see it grouped like…

   type Conf struct {
      Thing string `argp:”name:thing;short:t;desc:A thing you can do”`
   }
This would follow go struct tag standards.

d_burfoot

I might be the only person in this camp, but I find the "standard" command line arguments style absolutely repulsive. I write tons of CL code, and I always use easy key=value notation (sometimes it's flag=true, which I consider to be a minor sacrifice of conciseness in favor of consistency and readability).

bborud

It is more important to follow a common convention than agree on which looks better. If you are lucky you get used to common conventions and spend no time annoyed with them. If you are unlucky you will struggle to let it go. Struggling to let go is a disadvantage.

People who ignore convention and insist on whatever they like are never any fun to work with.

guappa

So to use your software one must reverse engineer your homemade parsing library because you disrespect any existing convention?

I'm glad I don't use anything you wrote :)

friendzis

There's already go implementation for docopt[0]

[0]: https://docopt.org/