Changing the Colour of 'ls' with dircolors and Environment Variables (Linux and Mac)

Unix knowledge is fractal. Every time you learn a detail, you find that detail has more details. It's impossible to learn it all. It's even very difficult to learn everything about a single one of Unix's older utility programs (Vim, sed, and awk come to mind, but there are many, many others). Today I'm going to look at a detail of ls, Unix's way of listing the contents of a directory.

Pretty much everyone who uses the command line in a Unix variant (all Linux distros, Macs) is aware that ls can be colourized: at this point, most distros set it up for you automatically. In Fedora, if you run alias, you'll find that the OS has already set up a default alias: alias ls='ls --color=auto' (this is common - although not guaranteed - in other distros as well). You can change those colours. If you're wondering why I would want to (isn't this a bit too ... niche? Too nit-picky?), here's why: I have trouble reading directory names because I use a black terminal background and ls is using dark blue text for directory names. Even on a Mac (which usually has a better screen than PCs) this can be quite hard to read.

The steps to change this are different on a Mac and Linux, presumably because they're using different implementations of ls. The Mac version is, unfortunately, much more limited. See Changing ls Colours on Mac below.

Changing ls Colours on Linux

There are, as usual, several possible ways to do this. I'll address the one that I know works first, and name the one I had trouble with later.

First, get a list of the default settings with dircolors -p. Note that this is the defaults: if you've changed any colours, it won't be reflected in this output. The command spews a bunch of lines across the screen: -p tells it to "list the database," namely its default settings. Let's do this again, but this time capture it and edit the output. First confirm that that you don't already have a file called ~/.dircolours, then run dircolors -p > ~/.dircolors.

Let's take a look at that file. We're going to ignore the section on Terminal types - because it works, because it's not about the colours we want to change, and because I don't understand it - and concentrate on the colourization. Here's the first section on filetypes: specific extensions come later. These types are given priority over file extensions: you've probably seen this. Think of zip files copied from a Windows partition: they always have the executable bit set, and as a result show the executable colours rather than the colours that an archive file usually has.

# Below are the color init strings for the basic file types.
# One can use codes for 256 or more colors supported by modern terminals.
# The default color codes use the capabilities of an 8 color terminal
# with some additional attributes as per the following codes:
# Attribute codes:
# 00=none 01=bold 04=underscore 05=blink 07=reverse 08=concealed
# Text color codes:
# 30=black 31=red 32=green 33=yellow 34=blue 35=magenta 36=cyan 37=white
# Background color codes:
# 40=black 41=red 42=green 43=yellow 44=blue 45=magenta 46=cyan 47=white
#NORMAL 00 # no color code at all
#FILE 00 # regular file: use no color at all
RESET 0 # reset to "normal" color
DIR 01;34 # directory
LINK 01;36 # symbolic link. (If you set this to 'target' instead of a
 # numerical value, the color is as for the file pointed to.)
MULTIHARDLINK 00 # regular file with more than one link
FIFO 40;33 # pipe
SOCK 01;35 # socket
DOOR 01;35 # door
BLK 40;33;01 # block device driver
CHR 40;33;01 # character device driver
ORPHAN 40;31;01 # symlink to nonexistent file, or non-stat'able file ...
MISSING 00 # ... and the files they point to
SETUID 37;41 # file that is setuid (u+s)
SETGID 30;43 # file that is setgid (g+s)
CAPABILITY 30;41 # file with capability
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
# This is for files with execute permission:
EXEC 01;32

dircolors -p has helpfully supplied us with a lot of comments. (It turns out a "DOOR" is a special file for client-server inter-process communication that's specific to Solaris.) One great thing about NeoVim (and Vim) is that if you open a .dircolors file in it, it will highlight the colour codes as the colours they represent: it's incredibly useful. If you're not seeing this, use :set syntax=dircolors in (Neo)Vim.

And by the way - the "blink" attribute really does work (not in Vim, but with the command line). It's utterly horrible, so you might consider using it for SETUID or SETGID, files you should never see - and should be very aware of if you do see them.

I made one change to start:

#DIR 01;34   # directory
DIR 44;33;01 # directory

Save the change, and then reload the file (this only applies to the current terminal): eval $(dircolors -b ~/.dircolors). (For Bash, Bourne Shell, or ZSH use -b as shown, for a C shell use -c). What that command does for you is execute the output of the dircolors -b ~/.dircolors command. That output looks like this:

LS_COLORS='rs=0:di=44;33;01:ln=47;36:mh=00:pi=40;33:so=01;35:do=01;35:bd=40;33;01:cd=40;33;01:or=40;31;01:mi=01;37;41:su=37;41:sg=30;43:ca=30;41:tw=30;42:ow=34;42:st=37;44:ex=01;32:*.tar=01;31:*.tgz=01;31:*.arc=01;31:*.arj=01;31:*.taz=01;31:*.lha=01;31:*.lz4=01;31:*.lzh=01;31:*.lzma=01;31:*.tlz=01;31:*.txz=01;31:*.tzo=01;31:*.t7z=01;31:*.zip=01;31:*.z=01;31:*.dz=01;31:*.gz=01;31:*.lrz=01;31:*.lz=01;31:*.lzo=01;31:*.xz=01;31:*.zst=01;31:*.tzst=01;31:*.bz2=01;31:*.bz=01;31:*.tbz=01;31:*.tbz2=01;31:*.tz=01;31:*.deb=01;31:*.rpm=01;31:*.jar=01;31:*.war=01;31:*.ear=01;31:*.sar=01;31:*.rar=01;31:*.alz=01;31:*.ace=01;31:*.zoo=01;31:*.cpio=01;31:*.7z=01;31:*.rz=01;31:*.cab=01;31:*.wim=01;31:*.swm=01;31:*.dwm=01;31:*.esd=01;31:*.jpg=01;35:*.jpeg=01;35:*.mjpg=01;35:*.mjpeg=01;35:*.gif=01;35:*.bmp=01;35:*.pbm=01;35:*.pgm=01;35:*.ppm=01;35:*.tga=01;35:*.xbm=01;35:*.xpm=01;35:*.tif=01;35:*.tiff=01;35:*.png=01;35:*.svg=01;35:*.svgz=01;35:*.mng=01;35:*.pcx=01;35:*.mov=01;35:*.mpg=01;35:*.mpeg=01;35:*.m2v=01;35:*.mkv=01;35:*.webm=01;35:*.webp=01;35:*.ogm=01;35:*.mp4=01;35:*.m4v=01;35:*.mp4v=01;35:*.vob=01;35:*.qt=01;35:*.nuv=01;35:*.wmv=01;35:*.asf=01;35:*.rm=01;35:*.rmvb=01;35:*.flc=01;35:*.avi=01;35:*.fli=01;35:*.flv=01;35:*.gl=01;35:*.dl=01;35:*.xcf=01;35:*.xwd=01;35:*.yuv=01;35:*.cgm=01;35:*.emf=01;35:*.ogv=01;35:*.ogx=01;35:*.aac=01;36:*.au=01;36:*.flac=01;36:*.m4a=01;36:*.mid=01;36:*.midi=01;36:*.mka=01;36:*.mp3=01;36:*.mpc=01;36:*.ogg=01;36:*.ra=01;36:*.wav=01;36:*.oga=01;36:*.opus=01;36:*.spx=01;36:*.xspf=01;36:';
export LS_COLORS

Yup: all of this is to set a single environment variable. You can do this by hand, but dircolors is your friend and makes it easier.

If you're using a 256 colour terminal, you can access all those other colours. The price is more complexity, and I don't think I'm going to use this much. Most distros default to 256 colours, but try to confirm. Checking this seems to be somewhat contentious: running tput colors sounds good, but in fact is only reporting the number of colours the terminal can support under the right circumstances ... which isn't necessarily true right now. So the best test would seem to be a practical one: run the colour test script I'll include at the end of this blog entry (256 Colours Test Script). But I usually just run echo $TERM which usually outputs xterm-256color which is generally enough proof.

To go 256 colours on your ~/.dircolors file, you'll need to do something like this:

# Ebook formats:
.epub  01;48;5;93
.lit   01;48;5;93
.mobi  01;48;5;93
.pdf   01;38;5;212;48;5;93

Any colour number above 16 requires you preface it with 38;5; for a foreground colour, and 48;5; for a background colour. 01 still indicates that the foreground should be bold. What the above lines do is put all of these file types on a purple background: the PDFs, which I wanted to stand out, have a pink foreground text, the others have a default colour foreground (light gray in my terminals).

I've seen mention, more than once, to the file /etc/DIR_COLORS as the place to change these settings system-wide. I've set values there, but seen no changes. I could of course load it with eval $(dircolors /etc/DIR_COLORS) but that would kind of defeat the whole system-wide thing. For now I'm sticking with ~/.dircolors for individual users. Run the eval statement in your ~/.bashrc and you should be good to go.

Changing ls Colours on Mac

On Mac, you need to set the $LSCOLORS environment variable - but the setting is very different, much more limited, and despite that reduced complexity, actually less intuitive. The default (per the Mac ls man page) looks like this:

LSCOLORS=exfxcxdxbxegedabagacad

The change I needed to make, to make my directories more readable:

LSCOLORS=Defxcxdxbxegedabagacad

Each pair of letters represents the foreground and background colours for a particular set of files, and the first pair is directories. There are 11 pairs, detailed below. I went from ex which is "blue-on-default" to De which is "yellow-on-blue."

The colours, from the man page:

a     black
b     red
c     green
d     brown
e     blue
f     magenta
g     cyan
h     light grey
A     bold black, usually shows up as dark grey
B     bold red
C     bold green
D     bold brown, usually shows up as yellow
E     bold blue
F     bold magenta
G     bold cyan
H     bold light grey; looks like bright white
x     default foreground or background

Likewise from the man page, the order of attributes:

1.   directory
2.   symbolic link
3.   socket
4.   pipe
5.   executable
6.   block special
7.   character special
8.   executable with setuid bit set
9.   executable with setgid bit set
10.  directory writable to others, with sticky bit
11.  directory writable to others, without sticky bit

Set LSCOLORS in your ~/.profile or ~/.bashrc and you should be ready.

256 Colours Test Script

#!/usr/bin/env bash
# filename: 256colours
# From https://unix.stackexchange.com/questions/269077/tput-setaf-color-table-how-to-determine-color-codes
# with some small tweaks.

bgcolour() {
    for c; do
        printf '\e[48;5;%dm %03d ' "${c}" "${c}"
    done
    printf '\e[0m \n'
}

fgcolour() {
    for c; do
        printf '\e[38;5;%dm %03d ' "${c}" "${c}"
    done
    printf '\e[0m \n'
}

IFS=$' \t\n'
echo "Base colours:"
bgcolour {0..15}
fgcolour {0..15}
echo ""
echo "The rest of the 256 colours:"
for ((i=0;i<6;i++)); do
    bgcolour $(seq $((i*36+16)) $((i*36+27)))
    fgcolour $(seq $((i*36+16)) $((i*36+27)))
    bgcolour $(seq $((i*36+28)) $((i*36+39)))
    fgcolour $(seq $((i*36+28)) $((i*36+39)))
    bgcolour $(seq $((i*36+40)) $((i*36+51)))
    fgcolour $(seq $((i*36+40)) $((i*36+51)))
done
echo ""
echo "Shades of gray (fine on Mac, Fedora, probably breaks on default Debian):"
bgcolour {232..243}
fgcolour {232..243}
bgcolour {244..255}
fgcolour {244..255}

If you don't like my script, the first answer at https://askubuntu.com/questions/821157/print-a-256-color-test-pattern-in-the-terminal offers a script with more compact output.