Changing the color output of tree

First problem to document this: how do you even capture the color output of the terminal?

Googled [copy terminal output with color].

First result: https://www.linuxquestions.org/questions/linux-desktop-74/preserve-colors-when-copy-pasting-from-terminal-943213/

Recommended package in Ubuntu repo: aha (ANSI HTML Adapter).

$ apt show aha
Package: aha
Version: 0.5-1
Priority: extra
Section: universe/utils
Origin: Ubuntu
Maintainer: Ubuntu Developers <ubuntu-devel-discuss@lists.ubuntu.com>
Original-Maintainer: Axel Beckert <abe@debian.org>
Bugs: https://bugs.launchpad.net/ubuntu/+filebug
Installed-Size: 48.1 kB
Depends: libc6 (>= 2.4)
Homepage: https://github.com/theZiz/aha
Download-Size: 13.2 kB
APT-Sources: http://es.archive.ubuntu.com/ubuntu focal/universe amd64 Packages
Description: ANSI color to HTML converter
 aha (ANSI HTML Adapter) converts ANSI colors to HTML, e.g. if you
 want to publish the output of ls --color=yes, git diff, ccal or htop
 as static HTML somewhere.

Install aha with apt. No surprises here.

Pipe the output of tree, forcing color with -C, to aha.

tree -C /media/isme/Samsung_T5/backup/ | aha

The output is complete HTML document. I can copy and paste the <pre> tag literally into a markdown document to capture the color output of my terminal.

/media/isme/Samsung_T5/backup/
├── config
├── data
│   └── 0
│       ├── 0
│       └── 1
├── hints.1
├── index.1
├── integrity.1
├── nonce
└── README

2 directories, 8 files

Now, you can see, dark blue on dark green is unreadable.

But tree renders normal folders with blue text on a transparent background. What kind of file is the “backup” folder?

ls -ld /media/isme/Samsung_T5/backup/ ~
drwxr-xr-x 102 isme isme   4096 Apr 24 11:31 /home/isme
drwxrwxrwx   1 isme isme 131072 Apr 23 21:06 /media/isme/Samsung_T5/backup/

The permissions show that the folder is world-writable. Does that make a difference?

And in any case, how do I change it?

From man tree:

Tree is a recursive directory listing program that produces a depth indented listing of files, which is colorized ala dircolors if the LS_COLORS environment variable is set and output is to tty.

I’m not sure what output is to tty refers to exactly. When tree is the only command in the pipeline, it outputs color to the terminal. When tree is piped into something else, it outputs color only with the -C option.

My .bashrc refers also to dircolors.

# enable color support of ls and also add handy aliases
if [ -x /usr/bin/dircolors ]; then
    test -r ~/.dircolors && eval "$(dircolors -b ~/.dircolors)" || eval "$(dircolors -b)"
    alias ls='ls --color=auto'
    #alias dir='dir --color=auto'
    #alias vdir='vdir --color=auto'

    alias grep='grep --color=auto'
    alias fgrep='fgrep --color=auto'
    alias egrep='egrep --color=auto'
fi

From man ls:

Using color to distinguish file types is disabled both by default and with --color=never. With --color=auto, ls emits color codes only when standard output is connected to a terminal. The LS_COLORS environment variable can change the settings. Use the dircolors command to set it.

LS_COLORS is set. But where is it set?

$ test -n "$LS_COLORS" && echo "LS_COLORS is set" || echo "LS_COLORS is not set"
LS_COLORS is set

The output of dircolors -b is a set of shell commands to set the variable! That’s why .bashrc uses eval to execute it.

$ dircolors -b
LS_COLORS='...';
export LS_COLORS

The .bashrc first attempts to read color configuration from ~/.dircolors before using the default colors. So I think to change the colors I can write such a file.

But where are the defaults?

From man dir_colors:

The program ls(1) uses the environment variable LS_COLORS to determine the colors in which the filenames are to be displayed. This environment variable is usually set by a command like

eval `dircolors some_path/dir_colors

found in a system default shell initialization file, like /etc/profile or /etc/csh.cshrc. (See also dircolors(1).) Usually, the file used here is /etc/DIR_COLORS and can be overridden by a .dir_colors file in one’s home directory.

The file /etc/DIR_COLORS does not exist on my system.

From man dircolors:

If FILE is specified, read it to determine which colors to use for which file types and extensions. Otherwise, a precompiled database is used. For details on the format of these files, run dircolors --print-database.

The database contains config for other-writable directories. We saw that the backup directory is other-writable.

STICKY_OTHER_WRITABLE 30;42 # dir that is sticky and other-writable (+t,o+w)
OTHER_WRITABLE 34;42 # dir that is other-writable (o+w) and not sticky
STICKY 37;44 # dir with the sticky bit set (+t) and not other-writable

But these values don’t appear in the LS_COLORS environment variable. The variable contains config only for file extensions.

$ echo $LS_COLORS
rs=0:di=01;34:ln=01;36:mh=00[...]

It turns out that the ls binary has hard-coded defaults. These are the defaults for version 8.30.

static struct bin_str color_indicator[] =
  {
    { LEN_STR_PAIR ("\033[") },		/* lc: Left of color sequence */
    { LEN_STR_PAIR ("m") },		/* rc: Right of color sequence */
    { 0, NULL },			/* ec: End color (replaces lc+rs+rc) */
    { LEN_STR_PAIR ("0") },		/* rs: Reset to ordinary colors */
    { 0, NULL },			/* no: Normal */
    { 0, NULL },			/* fi: File: default */
    { LEN_STR_PAIR ("01;34") },		/* di: Directory: bright blue */
    { LEN_STR_PAIR ("01;36") },		/* ln: Symlink: bright cyan */
    { LEN_STR_PAIR ("33") },		/* pi: Pipe: yellow/brown */
    { LEN_STR_PAIR ("01;35") },		/* so: Socket: bright magenta */
    { LEN_STR_PAIR ("01;33") },		/* bd: Block device: bright yellow */
    { LEN_STR_PAIR ("01;33") },		/* cd: Char device: bright yellow */
    { 0, NULL },			/* mi: Missing file: undefined */
    { 0, NULL },			/* or: Orphaned symlink: undefined */
    { LEN_STR_PAIR ("01;32") },		/* ex: Executable: bright green */
    { LEN_STR_PAIR ("01;35") },		/* do: Door: bright magenta */
    { LEN_STR_PAIR ("37;41") },		/* su: setuid: white on red */
    { LEN_STR_PAIR ("30;43") },		/* sg: setgid: black on yellow */
    { LEN_STR_PAIR ("37;44") },		/* st: sticky: black on blue */
    { LEN_STR_PAIR ("34;42") },		/* ow: other-writable: blue on green */
    { LEN_STR_PAIR ("30;42") },		/* tw: ow w/ sticky: black on green */
    { LEN_STR_PAIR ("30;41") },		/* ca: black on red */
    { 0, NULL },			/* mh: disabled by default */
    { LEN_STR_PAIR ("\033[K") },	/* cl: clear to end of line */
  };

The defaults in the ls binary match those in dircolors’ database.

So now how do you write a custom color config?

This Stack Overflow answer provides a solution. I can write the default database to the file read by my .bashrc.

dircolors --print-database > ~/.dircolors

Now in a new terminal session, I run ls again. So far it still works and the results are unchanged.

total 16896
drwxrwxrwx 1 isme isme   131072 Apr 23 21:06 backup
-rwxrwxrwx 1 isme isme 10091479 Apr 29  2020 SamsungPortableSSD_Setup_Mac.pkg
-rwxrwxrwx 1 isme isme  6817336 Apr 29  2020 SamsungPortableSSD_Setup_Win.exe
-rwxrwxrwx 1 isme isme      118 Jan 20  2016 Samsung portable SSD SW for Android.txt

Now I change the color for OTHER_WRITABLE files to transparent text on a blue background. Read man dir_colors to learn the color codes. I can’t find code 07;34 in the documentation, but it renders here as the inverse of the normal folder (blue text on transparent background).

OTHER_WRITABLE 07;34 # dir that is other-writable (o+w) and not sticky

Using vim to edit the .dircolors file is helpful because it parses the color codes and renders the code in the resulting color.

Testing ls with ls --color=always ~/media/isme/Samsung_T5 | aha:

total 16896
drwxrwxrwx 1 isme isme   131072 Apr 23 21:06 backup
-rwxrwxrwx 1 isme isme 10091479 Apr 29  2020 SamsungPortableSSD_Setup_Mac.pkg
-rwxrwxrwx 1 isme isme  6817336 Apr 29  2020 SamsungPortableSSD_Setup_Win.exe
-rwxrwxrwx 1 isme isme      118 Jan 20  2016 Samsung portable SSD SW for Android.txt

Testing tree with tree -C /media/isme/Samsung_T5/backup/ | aha:

/media/isme/Samsung_T5/backup/
├── config
├── data
│   └── 0
│       ├── 0
│       └── 1
├── hints.1
├── index.1
├── integrity.1
├── nonce
└── README

2 directories, 8 files

Done! Now I no longer have to be distracted by unreadable colors in the terminal.