Why?
Look at your home
$ find ~ -mindepth 1 -maxdepth 1 -type f -printf '%f\n' -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 '%f\n' -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:
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 config
- Easier to implement ACL
- 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
.
It can be difficult to constrain system access for an application that writes
files directly in $HOME
. Since all directories are specific to a
particular application, implementing access control patterns is easier.
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 '%s\n' "$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?
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