Random color selection
In my post about hostname-based prompt colors, I suggested
a fallback color scheme that was obviously wrong in order to
remind you to set a color for that host:
alice@unknown:~$
This carried with it an implicit assumption: you care what color each
host is assigned. You may instead be happy to assign a random color to
each host. We could use shuf
to generate a random color:
ps1_color="32;38;5;$(shuf -i 0-255 -n 1)"
The problem with this solution is the goal of the recoloring the prompt
was not simply to make it more colorful, but for that color to have
meaning. We want the color to always be the same for each login to a
given host.
One way to accomplish this would be to use that code to randomly
generate colors, but save the results in a table like the one used
before for manually-chosen colors. But it turns out we can do better.
Hash-based color selection
Hash functions have a useful property called
determinism, which means that hashing the same value will
always get the same result. The consequence is that we can use a hash
function like it's a lookup table of random numbers shared among all
of our computers:
ps1_color="32;38;5;$(($(hostname | sum | cut -f1 -d' ' | sed s/^0*//) % 256))"
The $((...))
syntax is bash
's replacement for expr
which is less portable but easier to use. Here we use it to make
sure the hash value we compute is a number between 0 and 255.
[sum
][sum] computes a hash of its input, in this case the result
of hostname
. Its output is not just a number so cut
selects
out the number and sed
gets rid of any leading zeros so it isn't
misinterpreted as octal.
The idea of using sum
was suggested by a friend after reading
my previous post on the topic.
But this turns out to not work great for hosts with similar names
like rob.example.com
and orb.example.com
:
alice@rob:~$
alice@orb:~$
Similar colors on hosts with very different names would not be so bad,
but because of how sum
works, it will tend to give similar results
on similar strings (although less often than I expected; it took some
effort to find such an example).
Better hash functions
While this is not a security-critical application, here
cryptographic hash functions solve the problem. Cryptographic
hash functions guarantee (in theory) that knowing that two inputs are
similar tells you nothing about their hash values. In other words,
the output of cryptographic hash functions are indistinguishable
from random and, in fact, they can be used to build
pseudorandom generators like Linux's /dev/urandom
.
The cryptographic hash function utilities output hex instead of decimal,
so they aren't quite a drop-in replacement for sum
:
ps1_color="32;38;5;$((0x$(hostname | md5sum | cut -f1 -d' ' | tr -d '\n' | tail -c2)))"
Here we use cut
and tr
to select just the hex string of the
hash. tail
's -c
option specifies the number of bytes to read
from the end, where 2 bytes corresponds to 2 hex digits, which can have
a value of 0 to 255, so the modulo operation is not needed. Instead the
0x
prefix inside $((...))
interprets the string as a hex number and
outputs it as a decimal number.
This code uses the md5sum
utility to compute an
MD5 hash of the hostname. This is recommended because
md5sum
is likely to be available on all hosts. Do be aware that
MD5 is insecure and it is only okay to use here because
coloring the prompt is not a security-critical application.
sha1sum
and sha256sum
are also likely available on modern
systems and work as drop-in replacements for md5sum
in the above
command should you wish to use a different hash. Additionally, you could
also get different values out of the hash by adding a salt:
salt="Some string."
ps1_color="32;38;5;$((0x$( (echo "$salt"; hostname) | sha256sum | cut -f1 -d' ' | tr -d '\n' | tail -c2)))"