Alexander Gromnitsky's Blog

ls colours

Latest update:

While reading about Poettering's adventures in determining the best $TERM value for serial & VM terminals in systemd, I accidentally discovered that ls from coreutils doesn't consult terminfo to check whether a terminal emulator (TE) supports colour.

What does it consult then? $TERM? Yes and no.

At first glance, with --color=auto flag, it ignores $TERM completely, trusting the LS_COLORS environment variable. But if $LS_COLORS is absent, it still prints in colour for some terminals--yet, simultaneously, not for all file types. What is going on?

You may have seen /etc/DIR_COLORS file. Usually, your distro includes some default sh scripts that invoke the dircolors(1) program with that file as an argument, generating a big, ugly looking string value for $LS_COLORS.

The interesting thing about /etc/DIR_COLORS is that it's just a copy of a file embedded into ls and dircolors programs (as a single static char const G_line[] variable) during compilation, meaning that in the absence of /etc/DIR_COLORS they technically still have a default colour scheme.

ls doesn't read that (probably modified by a user) file directly--it reads LS_COLORS environment variable, presumably because, in 1996, dynamically generating $LS_COLORS values was considered too costly. Hence, ls has a separate table (color_indicator[], for the curious) listing "important" file types. That table is consulted in case $LS_COLORS is unset.

But that doesn't explain why with no $LS_COLORS in sight, ls suddenly starts looking at $TERM. The DIR_COLORS file also contains a list of TEs ls considers capable of printing in colour. So why, then, is vt100 on that list when, according to the interwebs, nobody in 1978 in their right mind thought a colour terminal made any sense, unless they were driving a Lamborghini Countach? (I might be exaggerating a little.)

Anyhow, ls employs its embedded DIR_COLORS file to:

/* Check if the content of TERM is a valid name in dircolors.  */
static bool known_term_type(void) {
  char const *term = getenv("TERM");
  if (!term || !*term)
    return false;

  char const *line = G_line;
  while (line - G_line < sizeof(G_line)) {
    if (STRNCMP_LIT(line, "TERM ") == 0) {
      if (fnmatch(line + 5, term, 0) == 0)
        return true;
    }
    line += strlen(line) + 1;
  }

  return false;
}

See? Now it's all come together. Moreover, to please Lennart, in the next release of coreutils, vt220 will be joining the illustrious crew of colour terminals too. What a time to be alive.


Tags: ойті
Authors: ag