Making Bash compliant with the XDG Base Directory specification

GNU Bash does not comply with the XDG Base Directory specification, it uses the classical dotfiles approach where it just dumps all its files into the home directory. It does not even have the courtesy of putting all its files in one common ~/.bash directory. Fortunately we can coerce Bash into compliance with a bit of effort.

Changing where Bash reads configuration from

There are two ways Bash can be invoked: as an interactive shell, and as a login shell. We have to figure out which global files are sourced in both cases, and adjust the code to source our custom files instead of the standard ones. The files are as follows:

  • For login shells /etc/profile/

  • For interactive shells /etc/bash/bashrc

Reading the source of those files reveals some code similar to this snippet:

# Load profiles from /etc/profile.d
if [ -d /etc/profile.d/ ]; then
    for f in /etc/profile.d/*.sh; do
        [ -r "$f" ] && . "$f"
    done
    unset f
fi

What this means is that instead of changing the code of the global file and potentially turning it into a mess, we can instead write a script which does only one thing, place it in the designated directory and have it be sourced automatically. The exact directory might differ from operating system to operating system, so read your own global configuration file and the Bash documentation.

Login shells

Write the following code to the file /etc/profile.d/bash_xdg.sh:

# Make bash follow the XDG_CONFIG_HOME specification
_confdir=${XDG_CONFIG_HOME:-$HOME/.config}/bash
_datadir=${XDG_DATA_HOME:-$HOME/.local/share}/bash

# Source settings file
if [ -d "$_confdir" ] then
    for f in bash_profile bashrc; do
        [ -f "$_confdir/$f" ] && . "$_confdir/$f"
    done
fi

# Change the location of the history file by setting the environment variable
[ ! -d "$_datadir" ] && mkdir -p "$_datadir"
HISTFILE="$_datadir/history"

unset _confdir
unset _datadir

Interactive shells

The code for interactive shells is similar. The file in question is /etc/bash/bashrc.d/bash_xdg.sh

_confdir=${XDG_CONFIG_HOME:-$HOME/.config}/bash
_datadir=${XDG_DATA_HOME:-$HOME/.local/share}/bash

[[ -r "$_confdir/bashrc" ]] && . "$_confdir/bashrc"

[[ ! -d "$_datadir" ]] && mkdir -p "$_datadir"
HISTFILE=$_datadir/history

unset _confdir
unset _datadir

Closing thoughts

I have prefixed every file sourcing with a test to make sure the files in question actually exist. This will make sure that users who still use the old dotfiles approach will not be hindered. But what if a user has both an XDG directory file and a classic dotfile? I that case both will be sourced. This may or may not be what you actually want though. I don't think there is a way of preventing Bash from sourcing ~/.bashrc, so you will have to live with this quirk. You could print a warning message if both types of files are detected and urge users to settle on one.

There is one more files used by Bash, but it is not exclusive to Bash: ~/.inputrc from GNU Readline. Readline is what powers the interactive input and it can be used by other applications as well. Fortunately taking care of Readline is simple: just set the INPUTRC environment variable somewhere globally (e.g. in ~/.profile) to the location of your XDG file.