Files
uutils-args/design/arguments_in_coreutils.md
2023-02-19 15:02:30 +01:00

5.2 KiB

Argument Types and Behaviour in Coreutils

The coreutils are specified by POSIX and have various implementations. We want to be compatible with the GNU implementation. Generally, these utils use getopt_long function provided by GNUlib. This is a fairly simple parser, that you can repeatedly call to iterate over the options passed to the util.

Default Behaviour

This construction gives the follow default behaviours:

  • --help and --version are used as the flags for, well, help and version, respectively.
  • -h and -V are NOT excepted and sometimes even used for other purposes than showing help and version.
  • Values with leading hyphens are accepted by default.
  • getopt_long does not do any checking of conflicting arguments. Hence, all arguments have overriding behaviour, including overriding themselves.
  • Long options are inferred from unambiguous prefixes. For example, ls --group is inferred to ls --group-directories-first because there is no other long option starting with group.
  • The help string is written by hand and not provided by getopt_long. To their credit, the GNU authors have put great effort into standardizing these strings.

Many-to-many relationship

In the coreutils, there is a very loose coupling between the arguments and their effect in the program. Take the snippet from cat below, for example. Settings can be changed by multiple options (e.g. show_nonprinting is set by -t, -v and -A). This leads to a many-to-many relationship: each option can change multiple settings and each settings can be changed by multiple options.

switch (c) {
    case 't':
      show_tabs = true;
      show_nonprinting = true;
      break;

    case 'v':
      show_nonprinting = true;
      break;

    case 'A':
      show_nonprinting = true;
      show_ends = true;
      show_tabs = true;
      break;

    case 'E':
      show_ends = true;
      break;

    case 'T':
      show_tabs = true;
      break;
}

Argument Types

Flags

There are many simple flags that do not take any values. For example, the flags from cat above are all flags. They can have both long and short versions (--show-nonprinting & -v), but they can also have just one of the two.

Some flags are hidden, like tail's ---presume-input-pipe option. These hidden arguments also have 3 leading hyphens.

Options with values

Some options take values. Most of the time, this is just a long options. Some examples:

  • ls has -w, --width=COLS, where the value is required for both the short and long option.
  • ls has -F, --classify[=WHEN], where the value is optional for the long option and the short option does not take a value.
  • ls has --hyperlink[=WHEN], which does not have a short version (and an optional value).
  • mktemp has -p DIR, --tmpdir[=DIR], where the value is required for the short option and optional for the long option.
  • date has -I[FMT], --iso-8601[=FMT], where the value is optional for both the short and long option.

If the option takes one of several possible values, these values are inferred from unambiguous prefixes. For example, ls --color=y can be used as shorthand for ls --color=yes.

Positional arguments

Some utils take positional arguments, which might be required.

  • arch takes no positional arguments.
  • comm FILE1 FILE2 takes 2 required positional arguments.
  • tr SET1 [SET2] has 1 required and 1 optional positional argument.
  • uniq [INPUT [OUTPUT]] takes 2 optional positional arguments.
  • ls [FILE]... takes 0 or more positional arguments.
  • cp SOURCE... DEST take 1 or more source arguments and 1 required destination argument, however, cp -t DIRECTORY SOURCE... does not have the destination argument.
  • timeout DURATION COMMAND... takes one 1 required duration and a trailing argument of minimal 1 value. Any options appearing after the first value of COMMAND should be parsed as part of COMMAND.
  • who [ FILE | ARG1 ARG2 ] either takes 1 FILE argument or 2 ARG arguments.

Deprecated syntax +N and -N

Some utils (e.g. head, tail, kill, fold and uniq) support an old deprecated syntax where numbers can be directly passed as arguments as a shorthand. For example, uniq +5 is a shorthand for uniq -s 5 and uniq -5 is short for uniq -f 5.

These all behave slightly differently.

  1. head and tail only accept this if it is the first argument and either 1 or 2 arguments are given.
  2. In fold the -N must be standalone (e.g. -10b is rejected), but can appear at any position.
  3. In kill, the same rules as fold apply, but it can also be a name instead of a number.
  4. In uniq, the syntax does not need to stand alone and is additive in a weird way, because they hack -22 as -2 -2 so each flag -1...-9 multiplies the previous by 10 and adds itself. I'm not sure that we need to support this. Doing something like what fold and kill do is probably fine. Also note that to make it extra confusing, the + variant works like fold.
  5. pr the behaviour is similar to uniq.
  6. split seems to be somewhere between uniq and fold. It accepts things like -x10x correctly, but it doesn't do the additive thing from uniq across multiple occurrences. Basically, it's very clever and cursed.