Resolve XDG User Directories Reliably with xdg-user-dir

Hardcode a Downloads path in a script and it breaks; xdg-user-dir resolves the real one instead. This covers resolving a directory such as Downloads or Documents, plus a shell check that catches a surprisingly broad result. Examples use xdg-user-dir from the Debian package xdg-user-dirs, version 0.18-1build1 on this machine.

Allow about ten minutes. You need a normal shell and the package installed. These steps only read configuration and create no directories, so nothing here needs sudo or elevated privileges.

1. Check the command before using it

Confirm which executable your shell will run:

$ command -v xdg-user-dir
/usr/bin/xdg-user-dir

If the command is missing, install xdg-user-dirs through your distribution's package manager. Stop there if this is a restricted host you do not administer. Do not copy a replacement binary into a personal bin directory just to make a script pass its first check.

Checkpoint: the path should point at the installation you actually intend to use. In a script, use command -v during diagnostics, but call the command by name so normal package updates keep working.

2. Resolve a standard directory

Pass one of the names documented by the command:

$ xdg-user-dir DOWNLOAD
/home/your-user/Downloads

The accepted names are DESKTOP, DOWNLOAD, TEMPLATES, PUBLICSHARE, DOCUMENTS, MUSIC, PICTURES and VIDEOS. That name is a token, not a directory name: use the uppercase form the interface expects, and let the user's configuration supply the actual path.

Capture the result as a single shell value when another command needs it:

download_dir=$(xdg-user-dir DOWNLOAD)
printf 'Downloads: %s\n' "$download_dir"

Quote the variable. A user directory can contain spaces, and unquoted expansion turns one path into several arguments. The command prints the path on standard output, so keep diagnostic text out of that stream in a script.

3. See which configuration file is being read

xdg-user-dir reads user-dirs.dirs, located under XDG_CONFIG_HOME; when that variable is unset, the usual location is $HOME/.config/user-dirs.dirs. Inspect the effective path without modifying it:

config_home=${XDG_CONFIG_HOME:-$HOME/.config}
config_file=$config_home/user-dirs.dirs
printf 'Configuration: %s\n' "$config_file"
if [ -r "$config_file" ]; then
    sed -n '1,120p' "$config_file"
else
    printf 'Configuration is missing or unreadable\n' >&2
fi

A normal file contains assignments shaped like these:

XDG_DOWNLOAD_DIR="$HOME/Downloads"
XDG_DOCUMENTS_DIR="$HOME/Documents"

The tool expands the $HOME reference for the current user. Do not assume a directory exists just because its variable is present: check the resolved path separately if your program needs to open it:

download_dir=$(xdg-user-dir DOWNLOAD)
if [ -d "$download_dir" ]; then
    printf 'Using existing directory: %s\n' "$download_dir"
else
    printf 'Configured directory is absent: %s\n' "$download_dir" >&2
    exit 1
fi

4. Test an alternate configuration without touching your real one

Set XDG_CONFIG_HOME for one command when testing a profile, sandbox or deployment. This keeps the experiment separate from your normal desktop configuration:

$ env XDG_CONFIG_HOME=/path/to/test-config xdg-user-dir DOWNLOAD
/path/to/test-home/Downloads

The directory must contain a readable user-dirs.dirs file. Replace /path/to/test-config with a real test directory whose file uses the current user's intended $HOME value. The environment assignment applies only to this one invocation; it does not rewrite your account's settings.

Checkpoint: compare the result with the file you meant to test:

env XDG_CONFIG_HOME=/path/to/test-config xdg-user-dir DOWNLOAD
env XDG_CONFIG_HOME=/path/to/test-config sh -c 'cat "$XDG_CONFIG_HOME/user-dirs.dirs"'

Do not use sudo for this test: running it as another user changes HOME and can make a correct configuration look broken.

5. Guard against a misleading fallback

On the installed version, an absent mapping can resolve to the user's home directory, and a missing configuration file produces the same broad result. That output is a valid path, but it is not proof that the requested special directory is actually configured.

For a file operation, reject the home directory when a narrower destination is required:

download_dir=$(xdg-user-dir DOWNLOAD)
home_dir=$HOME
if [ "$download_dir" = "$home_dir" ]; then
    printf 'DOWNLOAD is not mapped beyond HOME: %s\n' "$download_dir" >&2
    exit 1
fi
printf 'Configured download directory: %s\n' "$download_dir"

That check is a policy choice for your own script, not a new promise from the command. If your application genuinely allows the home directory, drop the comparison and test only the operation you need.

Use only the documented names. Passing an arbitrary label is not a portable way to look up a custom directory: for a custom application path, use that application's own configuration or an explicit variable instead of treating xdg-user-dir as a general key-value store.

6. Change settings only with a recovery plan

This guide has deliberately made no persistent change. If you later edit user-dirs.dirs, back it up first and keep the original until applications have been checked:

config_file=${XDG_CONFIG_HOME:-$HOME/.config}/user-dirs.dirs
cp -- "$config_file" "$config_file.bak"

Editing that file changes where desktop applications look for a user's standard folders; it does not move existing files. Do not point a directory at a sensitive or shared location unless its ownership and permissions are intentional.

Recovery: restore the backup after checking it is the file you created:

config_file=${XDG_CONFIG_HOME:-$HOME/.config}/user-dirs.dirs
cp -- "$config_file.bak" "$config_file"
xdg-user-dir DOWNLOAD

The backup commands need write permission to the configuration directory, not root access. If the file is managed by a desktop setup tool, use that tool's documented update process instead of repeatedly overwriting its output.

Done means