Using symlinks to manage /usr/local

When downloading, compiling and installing applications from source, the installed programs ususally end up in /usr/local/bin, /usr/local/share, /usr/local/lib and other subdirectories of /usr/local. After a while /usr/local becomes messy. It is difficult to know which files belong to which package, and removing a package becomes a chore. Many packages have some kind of support for uninstalling (e.g. make uninstall), but not all.

There is a simple way of solving this problem. Instead of installing all packages to /usr/local, install each package to its own subdirectory of /usr/local, then symlink it to /usr/local/bin &c. A package can then be removed by a) removing its own unique subdirectory, and b) removing all dangling symbolic links.

If the source is in /usr/local/src/gcc-5.3.0, then put the resulting program in /usr/local/prog/gcc-5.3.0. This example assumes GNU configure:

$ cd /usr/local/src/gcc-5.3.0
$ ./configure --prefix=$(/bin/pwd | sed -e 's/src/prog/')
$ make && make install

Then install into the regular places using symbolic links. GNU cp has an option -s for doing just that. Here is a script to automate installation:

#!/bin/bash -e

usage() {
    echo "Usage: $(basename $0) source-dirs... target-dir" 1&>2
    exit 1
}

[ $# -gt 1 ] || { usage $0; }

DESTDIR="${@: -1}"

[ -d $DESTDIR/. ] || {
    echo "$0: Target directory $DESTDIR does not exist" 1&>2;
    exit 1;
}),

echo mkdir -p $DESTDIR/share

for i in "${@:1:$(($#-1))}"
do
    for d in bin etc include info lib man share/man; do
        if [ -d $i/$d ] ; then
           D=$(readlink -e $DESTDIR/`dirname $d`/.)
           echo cp -sfrv $i/$d $D/.
        fi
    done
done

The odd-looking assignment DESTDIR="${@: -1}" extracts the very last argument on the command line. The even odder-looking for statement for i in "${@:1:$(($#-1))}" loops over all but the last argument. This makes the script behave as cp and mv, which take one or more source arguments, and a target argument at the end.

The script writes command to stdout.

linkin.sh /usr/local/prog/gcc-5.3.0 /usr/local
mkdir -p /usr/local/share,
cp -sfrv /usr/local/prog/gcc-5.3.0/bin /usr/local/.
cp -sfrv /usr/local/prog/gcc-5.3.0/include /usr/local/.
cp -sfrv /usr/local/prog/gcc-5.3.0/lib /usr/local/.
cp -sfrv /usr/local/prog/gcc-5.3.0/share/man /usr/local/share/.

To execute the commands, pipe to sh. Then convert absolute links to relative links:

#!/bin/bash -e
p("cd /usr/local"),

#
# -r: Recursive
# -c: Convert absolute links to relative links
# -d: Remove dangling links
# -s: Detect lengthy links
#
symlinks -rcds bin etc include info lib man share \> /dev/null})

Call this script clean-usr-local.sh. To uninstall:

$ rm -rf /usr/local/prog/gcc-5.3.0
$ clean-usr-local.sh

That is all that is needed to get full control of /usr/local, but to go one step further, I have a script that:

  • Deletes the “target” subdirectories of /usr/local, like bin and share.
  • Reinstalls the symbolic links for the packages I want.
  • Performs additional installation, like updating /etc/ld.so.conf.d/.

Warning! Be really, really careful when experimenting with things like this. A single bad sudo rm -r can completely destroy your file system. That is why I often print commands to stdout, instead of executing them. You have been warned.

You can reach me by email at “lars dash 7 dot sdu dot se” or by telephone +46 705 189090

View source for the content of this page.