News for hackers. Stuff that matters.
Re:Conventions for Command Line Options by chriswarbo on Saturday August 01, 2020 @01:11PM (#24022098)
I write a lot of commandline utilities and scripts for automating tasks. I find environment variables much simpler to provide and accept key/value pairs than using arguments. Env vars don't depend on order; if present, they always have a value (even if it's the empty string); they're also inherited by subprocesses, which is usually a bonus too (especially compared to passing values to sub-commands via arguments, e.g. when optional arguments may or may not have been given).

Using arguments for key/value pairs allows invalid states, where the key is given but not the value. It can also cause subsequent, semantically distinct, options to get swallowed up in place of the missing value. They also force us to depend on the order of arguments (i.e. since values must follow immediately after their keys), despite it being otherwise irrelevant (at least, if we've split on '--'). I also see no point in concatenating options: it introduces redundant choices, which imposes a slight mental burden that we're better off without.

The only advice I wholeheartedly encourage from this article is (a) use libraries rather than hand-rolling (although, given my choices above, argv is usually sufficient!) and (b) allow a '--' argument for disambiguating flag arguments from arbitrary string arguments (which might otherwise parse as flags).

Re:Conventions for Command Line Options by TeMPOraL on Saturday August 01, 2020 @03:50PM (#24023348)
Some tangential musings:

I can't stop but see here parallels between cmdline arguments vs. environment variables for programs, and keyword arguments vs. dynamic binding for functions in a program (particularly in a Lisp one).

That is, $ program --foo=bar vs. $ FOO=bar program

seems analogous to:

  (function :foo "bar")
  ;; vs.
  (let ((*foo* "bar"))
    (function))
When writing code (particularly Lisp), keyword arguments are preferred to dynamic binding because the function signature is then explicitly listing arguments it uses, and dynamic binding (the in-process phenomenon analogous to inheriting environment from a parent process) is seen as dangerous, and a source of external state that may be difficult to trace/spot in the code.

I suppose the latter argument applies to env vars as well - you can accidentally pass values differing from expectation because you didn't know a parent process changed them. The former doesn't, because processes don't have a "signature" specifying its options, at least not in a machine-readable form. Which is somewhat surprising - pretty much all software written over the past decades follows the pattern of accepting argc, argv[] (and env[]), and yet the format of arguments is entirely hidden inside the actual program. I wonder why there isn't a way to specify accepted arguments as e.g. metadata of the executable file?

Re:Conventions for Command Line Options by aidenn0 on Saturday August 01, 2020 @06:04PM (#24024274)
Lisp will at least warn you if you bind a variable with earmuffs that isn't declared special though. Biggest downside to environment variables is the lack of warning if you misspell something.
Re:Conventions for Command Line Options by chriswarbo on Saturday August 01, 2020 @04:51PM (#24023811)
Interesting that you bring up Lisp and dynamic scope, since I've previously combined env vars with Racket's "parameters" (AKA dynamic bindings): https://lobste.rs/s/gnniei/setenv_fiasco_2015#c_owz2pp
Re:Conventions for Command Line Options by TeMPOraL on Saturday August 01, 2020 @05:17PM (#24023975)
Yeah, I just can't stop thinking about envs / dynamic binding when someone brings up the other.

Speaking of using both direct and dynamic arguments, a common pattern in (Common) Lisp is using dynamically-bound variables (so-called "special" variables) to provide default values. Since default values in (Common) Lisp can be given as any expression, which gets evaluated at function-call time, I can write this:

  (defun function (&key (some-keyword-arg *some-special*))
    ...)
And then, if I call this function with no arguments, i.e (some-function), the some-keyword-arg will pull its value from current dynamic value of some-special. In process land, this would be equivalent to having command line parser use values in environment variables as defaults for arguments that were not provided.
Re:Conventions for Command Line Options by jpitz on Saturday August 01, 2020 @06:03PM (#24024267)
In my head there's been a hierarchy for a long time.

when I build command line utilities and I think about the way that they'll be used, I tend to use configuration files for things that will change very slowly over time, and environment variables as a way to override default behaviors, and command line arguments to specify things that will often vary from invocation to invocation. In fact, most of the time, I use the environment variables either for development/testing features that I don't really intend to expose to most users, or for credentials that don't get persisted.

it's never occurred to me to use environment variables as a primary way to configure an application. I'll have to noodle on that for a while. My gut says that it's enough of a deviation from the Unix convention that I probably won't use that.

Re:Conventions for Command Line Options by Izkata on Saturday August 01, 2020 @10:55PM (#24025951)
> My gut says that it's enough of a deviation from the Unix convention that I probably won't use that.

It's not completely unheard of. Checking the man pages for a few I remember being affected:

* `ls` looks at LS_COLORS

* `grep` looks at GREP_OPTIONS, GREP_COLOR, GREP_COLORS, as well as the not-grep-specific LC_ALL, LC_COLLATE, LANG, etc, and POSIXLY_CORRECT

* `find` also looks at LC_%, LANG, POSIXLY_CORRECT as well, plus TZ and PATH for some of its other options

Looks like the program-specific ones LS_% and GREP_% can be overridden by options directly on the command, but the others don't have such options.

(Makefile wildcards because asterisk keeps doing italics)

Re:Conventions for Command Line Options by jpitz on Sunday August 02, 2020 @09:42PM (#24033390)
I feel like I didn't make "primary" clear enough - it would be like using an environment variable for the path for ls. That's gonna be a big no from me, dog.
Re:Conventions for Command Line Options by chriswarbo on Monday August 10, 2020 @11:10AM (#24110085)
> it would be like using an environment variable for the path for ls. That's gonna be a big no from me, dog

Me too. My point about env vars was limited to key/value options. I'm happy to use positional arguments when names are irrelevant, e.g. `ls foo/` is better than `DIR=foo/ ls` or `ls --dir foo/` or `ls --dir=foo/` or whatever, since the latter just adds noise and increases the chance we'll get something wrong.

I'm also happy to use arguments for flags which are either present or absent, where values are irrelevant, e.g. `ls --all` rather than e.g. `ALL=1 ls` or `ls --all=yes` or whatever. In this case the presence of a value is even more hamful, since the program might only be checking for the variable's presence, in which case `ALL=0 ls` would be misleading; plus we'd have to guess whether the value should be 0/1, yes/no, y/n, on/off, enable/disable, etc.

Re:Conventions for Command Line Options by Annatar on Sunday August 02, 2020 @02:05AM (#24026699)
These are all GNU/Linuxisms and are nowhere to be found in a real UNIX®️ like illumos, Solaris, HP-UX or IRIX64.

This is also a good example of why having GNU/Linux as one's first OS instead of a real UNIX®️ is so toxic.

Re:Conventions for Command Line Options by strokirk on Sunday August 02, 2020 @06:13AM (#24027669)
Is this a joke?
Re:Conventions for Command Line Options by Annatar on Thursday August 13, 2020 @02:47AM (#24140613)
No, it's a judgment on GNU, GNU/Linux and the horrid incompetence of contemporary information technology industry.
Re:Conventions for Command Line Options by arp242 on Saturday August 01, 2020 @08:28PM (#24025211)
There's a few reason I don't like environment variables for this: the first is that a random env variable can influence the operation of a program. Do "export foo=bar" and then maybe 30 minutes later you unexpectedly pass "foo". It's also hard to inspect; the output of "env" tends to be somewhat long, and you don't know which the program will pick up on. Flags are much clearer and more explicit.

There's also the issue that typo'ing "foo" as "fooo" will silently fail. Okay, that's a simple example, but some tools have "PROGRAMNAME_LONGDESCRIPTION". Being in all-caps also makes it hard to spot typos for me. You generally want your env vars to be long, so ensure they're unique.

Re:Conventions for Command Line Options by boring_twenties on Saturday August 01, 2020 @09:06PM (#24025429)
> It's also hard to inspect; the output of "env" tends to be somewhat long, and you don't know which the program will pick up on. Flags are much clearer and more explicit.

I don't think I disagree with your post, but this particular point seems to be trivially solved by prefixing the variable name with the program name?

IOW: `env | grep '^GIT_'` to see what you're passing to `git`.

Re:Conventions for Command Line Options by arp242 on Sunday August 02, 2020 @12:26PM (#24029617)
Yeah, fair enough; that's what I do as well.
Re:Conventions for Command Line Options by AnonC on Saturday August 01, 2020 @02:28PM (#24022749)
I’m curious if your preference for environment variables is only for writing programs or for using programs as well. From a user’s perspective, would you prefer that common commands use environment variables to get options? For example, would you prefer a “find” command that uses environment variables instead of command line options?
Re:Conventions for Command Line Options by chriswarbo on Saturday August 01, 2020 @05:01PM (#24023869)
> For example, would you prefer a “find” command that uses environment variables instead of command line options?

I use 'find' so much that my muscle memory would hurt if it changed, but it's actually a really interesting example. Its use of arguments to build a domain-specific language is a cool hack, but pretty horrendous; e.g.

    find . -type f -a -not \( -name \*.htm -o -name \*.html \)
We can compare this to tools like 'jq', which use a DSL but keep it inside a single string argument:

    key=foo val=bar jq -n '{(env.key): env.val}'
Note that 'jq' also accepts key/value pairs via commandline arguments, but that requires triplets of arguments, e.g.

    jq -n --arg key foo --arg val bar '{(env.key): env.val}'
Re:Conventions for Command Line Options by pklo on Saturday August 08, 2020 @04:43AM (#24090106)
The second example didn't work for jq-1.6 ---I had to write it as

  jq -n --arg key foo --arg val bar '{($key): $val}'
Re:Conventions for Command Line Options by chriswarbo on Monday August 10, 2020 @11:12AM (#24110106)
Yes you're right. I spotted it myself, but only after I couldn't edit anymore :(
Re:Conventions for Command Line Options by syshum on Saturday August 01, 2020 @04:33PM (#24023682)
That would depend on the target, the Grand Parent talks about Automation thus the target for their utilities is most likely the system not a user.

When writing automation routines env vars are often better form my experience

Tools that will be manually run by a user or admin then cli options are better

Re:Conventions for Command Line Options by pfranz on Saturday August 01, 2020 @05:19PM (#24023987)
I think there's a place for both, but env vars can make things really annoying to troubleshoot. Already, I often print all env vars before running automated commands and it can be a mess to dig through. Culling down environment variables when spawning a subprocess is difficult. Bad flags can error immediately if you made a typo. I've often misspelled an envvar and its hard to tell it did nothing (I think I saw a recent bug where trailing white space like "FOO " was the source of a years long bug). `FOO=BAR cmd` is also weird for history (although that's mostly a tooling issue).
Re:Conventions for Command Line Options by vbernat on Saturday August 01, 2020 @03:51PM (#24023357)
It seems equivalent to long options requiring a value. Also, if you mistype an environment variable, you won't get warned about it.
Re:Conventions for Command Line Options by ucarion on Saturday August 01, 2020 @04:05PM (#24023460)
Another nice thing about env vars is that they don't appear in the process table, unlike argv. For many applications, that makes them a suitable place to inject secrets, and makes them convenient to inject via wrapper tools.

For instance, the fact that the aws(1) cli tool and the AWS SDKs by default take IAM creds from AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, etc. means that you can write programs like aws-vault (https://github.com/99designs/aws-vault) which can acquire a temporary AWS session and then execute another command with the creds injected in the standard AWS_... env vars. For instance:

    aws-vault exec prod-read -- aws s3 ls
    aws-vault exec stage-admin -- ./my-script-that-uses-the-aws-sdk-internally
Also, passing arguments as env vars is part of the "12-factor app" design (https://12factor.net/config). That page has some good guidance.
Re:Conventions for Command Line Options by fanf2 on Saturday August 01, 2020 @06:10PM (#24024324)
The environment does appear in the process table: see `ps e` on Debian at https://manpages.debian.org/buster/procps/ps.1.en.html or ps -e on BSD at https://www.freebsd.org/cgi/man.cgi?query=ps&apropos=0&sekti... or pargs -e on Solaris at https://www.unix.com/man-page/opensolaris/1/pargs
Re:Conventions for Command Line Options by yjftsjthsd-h on Saturday August 01, 2020 @05:35PM (#24024081)
It's protected by at least uid, but `/proc/$PID/environ` does exist, and it might be exposed other ways, too.
Re:Conventions for Command Line Options by thayne on Saturday August 01, 2020 @08:11PM (#24025118)
That's actually a bad thing for non-secure arguments, and environment variables aren't much better for secure arguments.
Re:Conventions for Command Line Options by klhugo on Saturday August 01, 2020 @04:00PM (#24023412)
Quick comment, not an expert, but environ vars keep their state after the program is called. From a functional programming perspective, or just for my own sanity, wouldn’t it be more interesting to keep the states into minimum?
Re:Conventions for Command Line Options by chriswarbo on Saturday August 01, 2020 @04:45PM (#24023767)
You're right that mutable state should be kept to a minimum, but immutable state is fine. There usually isn't much need to mutate env vars.

Some thoughts/remarks:

- If we don't change a variable/value then it's immutable. Some languages let us enforce this (e.g. 'const'), which is nice, but we shouldn't worry too much if our language doesn't.

- We're violating 'single source of truth' if we have some parts of our code reading the env var, and some reading from a normal language variable. This also applies to arguments through.

- Reading from an env var is an I/O effect, which we should minimise.

- We can solve the last 2 problems by reading each env var once, up-front, then passing the result around as a normal value (i.e. functional core, imperative shell)

- Env vars are easy to shadow, e.g. if our script uses variables FOO and BAR, we can use a different value for BAR within a sub-command, e.g. in bash:

    BAR=abc someCommand
This will inherit 'FOO' but use a different value of 'BAR'. This isn't mutable state, it's a nested context, more like:

    let foo  = 123
        bar  = "hello"
        baz  = otherCommand
        quux = let bar = "abc"
                in someCommand
As TeMPOraL notes, env vars are more like dynamically scoped variables, whilst most language variables are lexically scoped.
Re:Conventions for Command Line Options by rabidrat on Saturday August 01, 2020 @05:40PM (#24024116)
That's only if you `export` the vars. You can do `FOO=1 BAR=2 cmd` and FOO and BAR will only have those values for the process and children. This is isomorphic to `cmd --foo=1 --bar=2`.
Re:Conventions for Command Line Options by treebog on Saturday August 01, 2020 @07:51PM (#24024990)
Absolutely, this is one of the reasons that environment variables are a nightmare to work with
Re:Conventions for Command Line Options by enriquto on Saturday August 01, 2020 @06:21PM (#24024397)
The beautiful thing of environment variables is that you can read them whenever you actually need them in your program. They pierce a hole through your call stack to the point that you need them. On the contrary, for command line arguments, you need to pass their values from the main function through whatever deep of the call stack you need them.
Re:Conventions for Command Line Options by carlmr on Saturday August 01, 2020 @06:34PM (#24024490)
I think that's an advantage. You know what goes in and out, you can see where it's passed and used. It provides transparency and fewer accidental behavioral changes.

E.g. I had a really weird bug in a script, git seemed to break for no reason, until i figured out that some other script had exported GIT_DIR which pointed to the git top level directory. The name of the variable isn't bad if want to save that directory. But git uses GIT_DIR as the location of the .git directory it should look at when it is defined.

Using environment variables is kind of like using global state, it can often cause weird behavior at a distance without a clear path of influence if you don't know exactly which environment variables are used by all your scripts.

Re:Conventions for Command Line Options by chriswarbo on Sunday August 02, 2020 @05:24AM (#24027488)
I'd say that's an example of "shotgun parsing", which is a bad idea since we might have already performed some irrevocable actions by the time we notice part of the input is missing/invalid ( https://stackoverflow.com/questions/50852226/what-does-shotg... ). It's better to check for and parse all input up-front https://lexi-lambda.github.io/blog/2019/11/05/parse-don-t-va...

I write a lot of tools in Haskell, where 'getEnv' is an "IO action" whose type prevents us using it inside most code. The simplest way to handle this ends up with a "functional core, imperative shell" model, e.g. most of my CLIs have the form (where 'interact' takes a pure String -> String function and uses it to transform stdio):

    main = do
      foo <- either error id . parseFoo <$> getEnv "FOO"
      interact (doTheThing foo)

    doTheThing :: Foo -> String -> String
    doTheThing = ...
This is also trivial to test. Some languages make it easy to replace env vars inside a single expression (e.g. bash), but others require us to set and reset the global environment, which is painful and error prone (especially if tests are run concurrently).
Re:Conventions for Command Line Options by arp242 on Saturday August 01, 2020 @08:32PM (#24025235)
In many languages you can always get at the argv list; e.g. sys.argv in Python, os.Args() in Go, ARGV in Ruby, etc.

It's not universal though; you can't in C or shell scripts for example (although, if you really want to, it's not hard to assign a global). I never had the need to inspect commandline arguments beyond the main() function though.

Re:Conventions for Command Line Options by Too on Sunday August 02, 2020 @03:17AM (#24026984)
Same thing can be said about global variables. I think any sane developer would agree it's a anti-pattern.
Re:Conventions for Command Line Options by chriswarbo on Sunday August 02, 2020 @04:57AM (#24027392)
Mutating global variables is certainly an anti-pattern, but I'm not so convinced that global constants are a problem; i.e. if we treat env vars like immutable variables (in fact I treat most variables as immutable).
Re:Conventions for Command Line Options by louwrentius on Sunday August 02, 2020 @04:52AM (#24027372)
If the command line arguments are getting to0 much, the next step is to use an config file, like an INI file and parse that.

Very simple and removes all the extensive command line clutter, especially if many settings stay the same.

I think environment variables are not very user friendly and not that visible.

Re:Conventions for Command Line Options by chriswarbo on Monday August 03, 2020 @06:10AM (#24035950)
Config files introduce a whole slew of complexity: race conditions, a whole bunch of new error conditions (permission denied, parse errors, read-only filesystems, disk full, etc.); system- or user-wide config files are spooky action-at-a-distance, which can make problems hard to track down, makes commands non-deterministic and may impact reproducibility (e.g. on other people's machines).

If commands insist on taking options via a config file, my preferred way to call them is to generate the config in-place via process substitution, e.g.

    someCommand -c <(printf 'foo\n\tbar="%s"\n\tbaz="%s"\n' "$BAR" "$BAZ")
Although this isn't always possible, e.g. if 'someCommand' tries seeking around the config file rather than just parsing it in one pass.
Re:Conventions for Command Line Options by andreareina on Sunday August 02, 2020 @03:44AM (#24027096)
Libraries can provide validation of key:value options. For example in python-land ‘click’ provides ‘nargs’ and ‘type’ parameters for the common case, and if those don’t suffice a custom coerce/validate callback.