Why?
Look at your home
$ find ~ -mindepth 1 -maxdepth 1 -type f -printf '%fn' -or -type d -printf '%f/n'
.alsoftrc
.android/
.ansible/
.aqbanking/
.audacity-data/
.bashrc
.cache/
.choosenim/
.code-d/
.config/
config/
.cpan/
data/
Desktop/
Downloads/
Documents/
.eclipse/
.elementary/
.emacs.d/
.emulator_console_auth_token
.flutter
.flutter_tool_state
.gem/
.ghc/
.ghidra/
.gnome/
.gnupg/
.godot/
.gore/
.gradle/
.gsutil/
.guestfish
.heekscad
.helioslauncher/
HomeBank-20210521.bak
HomeBank-20210607.bak
HomeBank-20210611.bak
.hushlogin
.idapro/
.ivy2/
.java/
javasharedresources/
.kb/
.kube/
.lldb/
.local/
.lunarclient/
.lyxauth
.m2/
macOS.vdi
.mcfly/
.metals/
.minecraft/
.mono/
.mozilla
.mputils/
.mume/
Music/
.omnisharp/
.ort/
.osc_cookiejar
.pack/
.paradoxlauncher/
.parsec/
Pictures/
.pki/
.pm2/
.profile
.pythonhist
.sbt/
.scim/
.ssh/
.steam/
.steampath
.steampid
.step/
.subversion/
.swt/
.tooling/
'Universe Sandbox'/
Videos/
.vscode/
.w3m/
.wine/
.xinitrc
.yarnrc
.zcompdump
.zoom/
.zshenv
What if your house was clean and tidy?
$ find ~ -mindepth 1 -maxdepth 1 -type f -printf '%fn' -or -type d -printf '%f/n'
.bashrc
.cache/
.config/
Desktop/
Downloads/
Documents/
.local/
Music/
Pictures/
.profile
Videos/
End users of your application (and hopefully yourself, as well) want a clean home directory. Instead of having files like ~/.gitconfig
scattered chaotically in the home folder, it would be better for them to reside in a dedicated configuration directory.
I like how Filip from 0x46.net
explained the issue from the perspective of a user:
My own home directory contains 25 ordinary files and 144 hidden files. The dotfiles contain data that doesn’t belong to me: it belongs to the programmers whose programs decided to hijack the primary location designed as a storage for my personal files. I can’t place those dotfiles anywhere else and they will appear again if I try to delete them. All I can do is sit here knowing that in the darkness, behind the scenes, they are there. . . . I dread the day in which I will hear a loud knock on my door and one of those programmers will barge in informing me that he is going to store a piece of his furniture in the middle of my living room, If I don’t mind.
Succinctly, following the spec means (by the very least) your application files are nearly categorized in subdirectories of the home folder. For instance, locations of cache files default to ~/.cache
while locations of config files default to ~/.config
, etc.
Benefits
If you aren’t already sold on a clean home directory, there are additional benefits.
- Easier to create backups
- Easier to share configuration settings
- Easier to isolate application state
- Easier to temporarily ignore the config
- Decreased reliance on hard-coded paths (flexibility + composability)
Since several directories (ex. ~/.config
, by default) represent a discrete class of application files, it is much easier to make rules for a specific category of files during backup.
Since all settings are in a single directory, they can more easily be shared across computer systems.
Since all data specific to a single machine is in a single directory, you can easily avoid sharing it between systems when sharing data or backups.
Not all applications have a --no-config
option (especially GUI applications), and those that do have a --config
option do not always work when using /dev/null
as a file, shell process substitution, or a simple empty file. It is easiest to set XDG_CONFIG_HOME=/tmp/dir
.
As a more concrete example, imagine that /etc
did not exist – configuration would be chaotically scattered about in a way similar to how $HOME
exists today. But, because we have /etc
specifically designated as a directory for config files (along with other directories) by the File Hierarchy Standard, it is significantly easier to locate, edit, and backup system files across machines. Those ergonomics should not just exist for system-level files, but for the user-level as well.
Thus, I implore, I beg all you application developers to implement the XDG Base Directory Specification.
How?
First, categorize the files that your program needs to write into four categories:
- Configuration
- Data
- State
- Cache
Configuration files that affect the behavior of an program. Even if the configuration file is not meant to be human-editable, it likely belongs here.
General files and data that is inherently portable across computers. Examples include fonts, files downloaded from the internet, and desktop entries.
Files that hold the state of the application. This may include logs, command history, recently used files, and game save data. In other words, if the data is unique for a given machine, the file belongs here.
Cache files.
Each aforementioned category is mapped to a special environment variable, which is the directory that contains all files of that category. For example, “Configuration” files are all stored in $XDG_CONFIG_HOME
. If that environment variable is invalid, then the default value of $HOME/.config
should be used instead. See the full table below:
Category | Environment Variable | Default Value | FHS Approximation |
---|---|---|---|
Configuration | $XDG_CONFIG_HOME |
$HOME/.config |
/etc |
Data | $XDG_DATA_HOME |
$HOME/.local/share |
/usr/share |
State | $XDG_STATE_HOME |
$HOME/.local/state |
/var/lib |
Cache | $XDG_CACHE_HOME |
$HOME/.cache |
/var/cache |
There are three ways the environment variable can be invalid:
- If it is unset (
unset XDG_CONFIG_HOME
) - If it is set to an empty value (
XDG_CONFIG_HOME=""
) - If it is not an absolute path (
XDG_CONFIG_HOME="./config"
)
Code examples
Now that you’re acquainted with the standard, you probably want to sees some code examples. The following programs are for Linux only (See the FAQ below for details on MacOS and Windows). Of course, if you are actually writing a program, I recommend using a library.
The following is Go 1.13+ code. Note that it does not use os.UserConfigDir (Source) since that does not silently ignore non-absolute paths as per the spec
Show Go
package main import ( "os" "fmt" "log" "path/filepath" ) func getConfigDir() (string, error) { configDir := os.Getenv("XDG_CONFIG_HOME") // If the value of the environment variable is unset, empty, or not an absolute path, use the default if configDir == "" || configDir[0:1] != "/" { homeDir, err := os.UserHomeDir() if err != nil { return "", err } return filepath.Join(homeDir, ".config", "my-application-name"), nil } // The value of the environment variable is valid; use it return filepath.Join(configDir, "my-application-name"), nil } func main() { config_dir, err := getConfigDir() if err != nil { panic(err) } fmt.Println(config_dir) }
The following is Python 3.5+ code. It uses Path.home (Source) (which uses os.path.expanduser (Source))
Show Python
import sys, os from pathlib import Path def get_config_dir() -> str: config_dir = os.getenv('XDG_CONFIG_HOME', '') // If the value of the environment variable is unset, empty, or not an absolute path, use the default if config_dir == '' or config_dir[0] != '/': return str(Path.home().joinpath('.config', 'my-application-name')) // The value of the environment variable is valid; use it return str(Path(config_dir).joinpath('my-application-name')) config_dir = get_config_dir() print(config_dir)
The following is Bash code. Note that it does not use "${XDG_CONFIG_HOME:-$HOME/.config}"
since that does not silently ignore non-absolute paths as per the spec
Show Bash
get_config_dir() { unset REPLY; REPLY= local config_dir="$XDG_CONFIG_HOME" # If the value of the environment variable is unset, empty or not an absolute path, use the default if [ -z "$config_dir" ] || [ "${config_dir::1}" != '/' ]; then REPLY="$HOME/.config/my-application-name" return fi # The value of the environment variable is valid; use it REPLY="$config_dir/my-application-name" } get_config_dir printf '%sn' "$REPLY"
FAQ
Is that it?
There is more to the standard, but you should know and implement at least the aforementioned, which is the most important part. If you don’t want to implement this yourself, use a library. By implementing this, your users will silently thank you!
What if I already do the wrong thing and use the home folder directly?
If you write to a few individual files in $HOME
, simply check if those files exist before following the XDG Base Directory Specification. Consider the following example, given a traditional config location of ~/foo-app.json
: If ~/foo-app.json
exists use it; if it does not exist, then check if $XDG_CONFIG_HOME
is set and is valid. If it is, then write to $XDG_CONFIG_HOME/foo-app/foo-app.json
. If not, then write to $HOME/.config/foo-app/foo-app.json
.
However if you hold all configuration and data inside a subdirectory of home (ex. ~/.ansible
), things are a bit less clear-cut. Moving everything to $XDG_STATE_HOME/application-dir
would be easiest to the application developer, but less semantically correct. If you don’t want to worry about migrating multiple files to separate directories, it would be a good first start to make the location of the directory configurable by an environment variable. For example, Terraform exposes this option using TF_DATA_DIR
. Even just exposing the directory through an environment variable is incredibly useful.
Who else does this?
KittyFontForgeImageMagickAlacrittyComposerTerminatorclangdchezmoiaria2bati3nanopicompoetryVLCawesomeaercmicroHandbrakeOfflineIMAPpolybarrclonexmonadmesaGodotDockerAnkihttpiecitrabasherasunderTransmissionhtopTermiteGitKakouneBlenderGstreamerrangerPryTypeScriptnavid-feetMercurialLibreOfficeAudaciousbyobucolordiffcmusccacheantimicrolftpmccalcurseDelugeTerrariaWiresharkswayxsettingsdtmuxPulseAudioneomuttVirtualBoxbroothttpieALSApandocnbtigcargoopenboxasdffishfontconfigDolphinMultiMCpnpmGIMPbinwalkInkscapeiwdmpvjosmWechat
See the Arch Wiki for a longer list and more details.
I already provide a --config
option. Do I really need this?
Absolutely! The ability to specify files or directories with a command-line argument cannot consistently be used. For example, you could define a shell alias, but it won’t always be used since the Bash option expand_aliases
is unset by default in a non-interactive environment. Furthermore, if your program is invoked in any programmatic way (ex. an exec style syscall), there is no reasonable way to ensure that flag is passed.
Should I do this on macOS?
I’ve done quite a bit of research, and there doesn’t seem to be a common consensus. Generally speaking, if your program’s only interface is a CLI, then it’s permissible to follow the XDG Base Directory Specification, even if you aren’t on Linux. This seems to be pretty common across the ecosystem, especially for Bash programs. On the other hand, if it’s a GUI, then you would usually follow the standard macOS directory locations. For example, Sublime Text 3 stores it’s preferences on macOS in ~/Library/Application Support/Sublime Text 3/Packages/User
, even if it also ships with a subl
command.
Should I do this on Windows?
I don’t have a straight answer for you, simply because I don’t use Windows frequently enough. I’ll add that Windows already has a preexisting directory to place user data. Notwithstanding this fact, widely-used applications like scoop follow the spec for configuration. Another command line app, PSKoans hard-codes ~/.config
. Admittedly, its usage is not widely prevalent, especially compared to macOS, so the answer would be closer to a “no”.
Who are you?
I’m Edwin Kofler, a software developer that wants to proliferate the knowledge (and implementation) of this specification to a wider group of developers. I’ve opened issues (that are now fixed) in repositories such as Poetry and pnpm, and have proposed PRs (that are now merged) in repositories such as osc, vscode-kubernetes-tools, mume, basher, and more! Together, I hope we can make the home directory clean again!
Auxillary resources
- Official spec
- Debian wiki
- Arch Linux wiki
- libetc: A shared library using the
LD_PRELOAD
trick to force storing files/directories in the proper place - boxxy: Script that uses bind-mounting shenanigans to force storing files/directories in the proper place
- xdg-ninja: a shell script that checks
$HOME
for unwanted files and directories
Leave A Comment