[HN Gopher] argp: GNU-style command line argument parser for Go
       ___________________________________________________________________
        
       argp: GNU-style command line argument parser for Go
        
       Author : networked
       Score  : 134 points
       Date   : 2025-03-23 12:45 UTC (10 hours ago)
        
 (HTM) web link (github.com)
 (TXT) w3m dump (github.com)
        
       | arccy wrote:
       | > * multiple options can be combined: -abc is the same as -a -b
       | -c
       | 
       | > * option values can be separated by a space, equal sign, or
       | nothing: -a1 -a=1 -a 1 are all equal
       | 
       | > * 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]
       | 
       | while convenient, one of the reasons I dislike gnu style flags is
       | just how inconsistent they can be and annoyingly hard to parse by
       | humans
        
         | nine_k wrote:
         | Understandable! This is because `-a 1 2 3` assiging a = [1, 2,
         | 3] is not GNU style.
         | 
         | If you want that, the GNU style could be `-a "1 2 3"`.
         | 
         | Normally `-a 1 2 3` would be a = 1, and 2 and 3 being
         | positional arguments. A clarified way to write the same would
         | be `-a 1 -- 2 3`.
        
       | acmj wrote:
       | 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.
        
         | cratermoon wrote:
         | 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.
        
           | hnlmorg wrote:
           | As a heavy user of Go tooling, I still really dislike Gos
           | style.
        
             | bborud wrote:
             | Agree. It was an unnecessary thing to do.
        
           | acmj wrote:
           | 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 wrote:
             | 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.
        
           | treyd wrote:
           | 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 wrote:
             | > The Go community has consistently shown it's not very
             | good at making well-thought out decisions.
             | 
             | Please, do name one language that has.
        
           | arp242 wrote:
           | > 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.
        
         | nloomans wrote:
         | 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 wrote:
           | 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.)
        
             | cb321 wrote:
             | 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).
        
               | duckerude wrote:
               | The problem is existing shell scripts and muscle memory
               | and command histories. `cut -d=` has always worked and
               | works on all the other implementations so it should keep
               | working if you switch to uutils.
        
               | cb321 wrote:
               | Literally any short option key that takes a string or a
               | char that could legitimately start with '=' has the
               | problem, though, not just `cut`. The '=' will be "eaten"
               | by one tool and left in another. But you know that. You
               | write a bit as if we disagree, but I don't see any real
               | point of contention. :-) Also, uutils has a very strict
               | "drop-in" agenda. So, as another e.g., if you want `cp
               | -t=foo` or `cp -S=foo` to work the same, you're going to
               | have trouble if that '=' is eaten.
               | 
               | So, in this case that would seem to imply a problem for
               | any utilities with options taking strings-or-chars, not
               | merely `cut -d`. If uutils really wants to be strictly
               | drop-in compatible, they may well need to roll their own
               | option parser or twist the arm of whoever's they use to
               | provide a mode for them.
               | 
               | In the more general case, "cross compatibility" may just
               | always be limited by the reality that people just
               | disagree on this stuff "more than they seem to _think_
               | they do " (at least in my experience) and definitely more
               | than they wish they did. I surveyed my /usr/bin once and
               | like half of thousands of commands did not work with
               | --help (yes, running that took some confidence in
               | backups! but anyone could replicate on a throwaway VM or
               | something). Consistency is nice, but consistency with
               | what? -l=foo is consistent with --long=foo, but not
               | (some, but not other) historical things.
               | 
               | I'm not sure there will ever will be a world in which you
               | don't need to know which PLang/CL toolkit was used to
               | make a CLI utility if you _really_ want to know its
               | syntax. The article 's lib is going its own way from the
               | Go stdlib. POSIX is pretty darn calcified. A 15 year old
               | Python stdlib thing is unlikely to ever change in this
               | regard. Python also allows "--beg" for "--beginning-long-
               | option" if nothing else starts with "--beg" even back in
               | its optparse days and that also tends to be
               | controversial. cligen tools actually provide a --help-
               | syntax. Maybe something like that could take off?
        
               | duckerude wrote:
               | I can think of a lot of cases where it theoretically
               | _could_ be a problem, but `cut -d=` is the only one I 've
               | found so far where an end user ran into trouble because
               | of this ambiguity, and I think it's the only one for
               | which uutils bothers implementing a workaround. That's
               | why I give it special attention.
               | 
               | > You write a bit as if we disagree, but I don't see any
               | real point of contention. :-)
               | 
               | The `cut -d:=` spelling solves a different problem than
               | the one I meant (and the one you're now talking about).
               | But we're mostly on the same page!
        
             | ucarion wrote:
             | 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.
        
             | edoceo wrote:
             | 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 '='.
        
           | cb321 wrote:
           | 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).
        
         | Arch-TK wrote:
         | 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 wrote:
           | 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.
        
         | marxisttemp wrote:
         | No, command tools should follow the POSIX standards, not GNU.
        
           | masklinn wrote:
           | You're almost right. No command tools should follow the POSIX
           | standard. A standard which all but forbids long options
           | deserves only scorn.
        
           | NekkoDroid wrote:
           | 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.
        
           | greyw wrote:
           | 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.
        
         | ants_everywhere wrote:
         | 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.
        
           | gjvc wrote:
           | GNU became the de-facto standard at least 20 years ago
        
           | acmj wrote:
           | 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.
        
       | amelius wrote:
       | Does it throw an exception when it fails to parse?
        
         | johnisgood wrote:
         | According to the example and judging by the code at a quick
         | glance, it does not, it seems to exit, which is unfortunate.
         | Perhaps the author may correct me.
        
       | latchkey wrote:
       | 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 wrote:
         | 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.
        
           | latchkey wrote:
           | Kong doesn't explicitly say it supports GNU-style, which is
           | why I didn't mention it.
        
           | peterldowns wrote:
           | 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 wrote:
             | 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.
        
           | networked wrote:
           | 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 wrote:
             | > _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?
        
               | networked wrote:
               | Yes, see https://github.com/alecthomas/kong/issues/315.
               | It may be nontrivial to change.
        
           | donatj wrote:
           | 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.
        
         | programd wrote:
         | 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.
        
         | hujun wrote:
         | 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)
        
       | jmclnx wrote:
       | If you need long options because you are running out of letters,
       | time for a rewrite :)
        
       | dotemacs wrote:
       | 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.
        
         | PhilipRoman wrote:
         | `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 wrote:
           | 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)
        
             | orthoxerox wrote:
             | As was `dd`, another program with an unorthodox syntax.
        
         | darrenf wrote:
         | > _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.
        
       | edoceo wrote:
       | I use Kong https://github.com/alecthomas/kong
       | 
       | I does everything, including too complicated options.
        
       | d_burfoot wrote:
       | 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 wrote:
         | 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.
        
       | sandreas wrote:
       | 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 :-)
        
       | thrill wrote:
       | Sanity!
        
       | CamouflagedKiwi wrote:
       | 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 wrote:
         | 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 wrote:
           | 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.
        
       | reactordev wrote:
       | 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.
        
       | braggerxyz wrote:
       | I really favor posix style, which is why I use pflags package for
       | my cli Tools.
        
       | odc wrote:
       | The standard flag package in Go also supports GNU style flags.
       | 
       | https://pkg.go.dev/flag#hdr-Command_line_flag_syntax
        
       | declan_roberts wrote:
       | Assigning values like -v as an int and allowing -vvvv to be
       | easily parsed is a great compatibility feature for existing
       | utils.
        
       | synergy20 wrote:
       | i hope the below is agreed on, mycmd -h, and mycmd --help, are
       | the same, - for single letter,-- for longer format
       | 
       | same to mycmd -v, mycmd --version
        
       ___________________________________________________________________
       (page generated 2025-03-23 23:00 UTC)