Skip to content

Throw an error when non-nullary short command-line options are interpreted unintuitively #15186

@tfkls

Description

@tfkls

Is your feature request related to a problem?

As it currently stands, a chain of short command-line options is expanded and treated as if the chained options were provided separately. That is:

  • a -vvv option is expanded as 3 arguments: -v -v -v
  • a -vc option is expanded as 2 arguments: -v -c
  • a -cv option is expanded as 2 arguments: -c -v

While this behaviour is fine for nullary options like -v, it causes issues with options which take additional arguments.

In particular, if we take the last example from the previous list, it causes issues with nix shell where -c is the short option of --command which takes arguments:

  • nix shell nixpkgs#hello -cv hello gets expanded to nix shell nixpkgs#hello -c -v hello and nix tries to run -v as the executable.

This behaviour is:

  • as far as I'm aware undocumented;
  • and (in my opinion) very unintuitive.

Note that while -c is a bit special (as it consumes all of the following arguments), this issue persists with unary and binary options as well, in both flake'y and non-flake'y commands – for an incomplete list of examples:

  • -k, -s, -i in nix shell & nix run
  • -A and -I in nix-shell

Proposed solution

Make the argument parser throw a human-readable error when a non-nullary short option is provided as part of the chain in any position other than the last.

That is, taking nix shell as an example:

  • -cv would throw an error, as the non-nullary -c is not the last option in the chain;
  • -vc would still work, as the non-nullary -c is the last option in
    the chain;
  • -vvv would still work, as it contains no non-nullary options.

As another example, for nix-shell:

  • -EI, -KA and -vj10 would still work;
  • while -IA, -Ik and -Aj would throw the new error message.

Alternative solutions

  • Treat everything after the first non-nullary option as the argument to said option
    • This would make -chello or -Ihere equivalent to -c hello and -I here;
    • Some existing command-line tools (e.g. GNU tar's -f, gcc's -I) use this logic;
    • However, this also causes some issues:
      • a transposition like -cv instead of -vc would call v – this is arguably more risky than the status-quo, as single-letter commands might exist and have undesired side-effects, while commands starting with - are almost never seen.
      • syntax for binary options like -s/--set-env-var might be a bit weird.
  • Disallow short-option chaining
    • Too inconvenient, especially for verbosity (-vvv)
  • Disallow short-option chaining for non-nullary options
    • That is, reject -vc as well and only allow -vvv and similar chains of nullary options
    • This is a bit inconvenient and inconsistent with other command-line utilities
  • Document the current behaviour
    • I believe this to be inadequate, as the current behaviour is unintuitive and is a consequence of the internal parsing implementation details leaking into the outward interface.

Additional context

If this gets accepted, I plan to implement this and send in a patch (I believe it's enough to change the logic src/libutil/args.cc to make the proposed solution work).

The main reason for creating this issue beforehand, is that I am not sure whether this is would be a breaking change or not: there may be some users which depend on the undocumented behaviour of -ci running the -i executable or on -Ik including the -k directory.
(I find it improbable, but Hyrum's law applies).

Checklist


Add 👍 to
issues you find important.

Metadata

Metadata

Assignees

No one assigned

    Labels

    cliThe old and/or new command line interfacefeatureFeature request or proposal

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions