关于 xdg-base-directory
指导开发者在 CLI 工具及其他应用中遵循 XDG 基础目录规范,选择文件存储路径。内容包括环境变量处理、目录优先级,以及 Python 标准库和 platformdirs 实现方式。
主要功能
- 01配置、数据、状态与缓存目录指南
- 02绝对路径及默认值校验
- 03环境变量优先级与搜索路径
- 04Python 标准库和 platformdirs 实现模式
- 05XDG 合规性测试指南
适用场景
- 01为 CLI 工具选择文件存储路径
- 02迁移隐藏的用户目录文件夹
- 03支持自定义 XDG 环境变量
- 04测试目录覆盖设置
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ jamie-bitflight/ claude_skills --skill xdg-base-directory安装后在 Claude Code 中输入/xdg-base-directory确认已加载。
详细介绍
XDG 基本目录规范
按照 XDG 基本目录规范,实现配置、数据、缓存和状态文件的标准化目录路径。
何时使用此技能
在以下情况下使用此技能:
- 创建用于存储用户专属文件的 CLI 工具或应用程序
- 为 Linux/Unix 应用程序实现配置文件管理
- 构建需要标准化目录路径的跨平台 Python 应用程序
- 将旧版应用程序的路径从
~/.appname迁移到符合 XDG 规范的路径 - 为 Python 软件包设计文件存储架构
- 实现用户目录的正确环境变量处理
- 编写遵循用户配置的目录首选项的应用程序
- 使用自定义 XDG 目录覆盖项测试应用程序
核心规范规则
XDG 基本目录规范(版本 0.8,2021 年 5 月)规定了以下关键要求:
1. 所有路径都必须是绝对路径
环境变量的值必须是绝对路径。相对路径无效,必须忽略。
def validate_xdg_path(path_str: str | None) -> Path | None:
"""Validate XDG path is absolute per specification."""
if not path_str:
return None
path = Path(path_str)
if not path.is_absolute():
return None # Ignore relative paths per spec
return path
2. 空变量使用默认值
如果环境变量未设置或为空,则使用规范定义的默认值。
3. 搜索路径的优先级顺序
- 用户专属目录(
$XDG_CONFIG_HOME、$XDG_DATA_HOME)优先 - 按顺序搜索系统目录(
$XDG_CONFIG_DIRS、$XDG_DATA_DIRS) - 采用第一个匹配项
4. 搜索路径使用冒号分隔符
$XDG_DATA_DIRS 和 $XDG_CONFIG_DIRS 使用冒号(:)作为路径分隔符,与 $PATH 类似。
环境变量
用户专属目录(单一路径)
| 变量 | 用途 | 默认值 | 用途示例 |
|---|---|---|---|
$XDG_CONFIG_HOME | 用户配置文件 | $HOME/.config | 设置、首选项、用户配置 |
$XDG_DATA_HOME | 用户数据文件 | $HOME/.local/share | 数据库、生成的内容、持久化数据 |
$XDG_STATE_HOME | 用户状态文件 | $HOME/.local/state | 日志、历史记录、撤销缓冲区、最近使用的文件 |
$XDG_CACHE_HOME | 用户缓存文件 | $HOME/.cache | 临时数据、下载的文件、构建产物 |
$XDG_RUNTIME_DIR | 用户运行时文件 | 由系统设置(/run/user/$UID) | 套接字、管道、锁文件、IPC |
系统范围目录(搜索路径)
| 变量 | 用途 | 默认值 |
|---|---|---|
$XDG_DATA_DIRS | 系统数据搜索路径 | /usr/local/share/:/usr/share/ |
$XDG_CONFIG_DIRS | 系统配置搜索路径 | /etc/xdg |
用户可执行文件(约定,不属于 XDG)
| 路径 | 用途 |
|---|---|
$HOME/.local/bin | 用户专属的可执行文件 |
注意:发行版应确保 $HOME/.local/bin 出现在 $PATH 中。
目录选择指南
根据数据特征选择合适的目录:
在以下情况下使用 $XDG_CONFIG_HOME:
- 应用设置和偏好
- 用户专属的配置文件
- TOML/YAML/JSON 配置
- 示例:
~/.config/myapp/config.toml
在以下情况下使用 $XDG_DATA_HOME:
- 持久化的应用数据
- 用户生成的内容
- 下载的模型或资源
- 数据库文件
- 示例:
~/.local/share/myapp/models/model.gguf
在以下情况下使用 $XDG_STATE_HOME:
- 操作历史(命令历史、撤销缓冲区)
- 可重新生成的应用状态
- 最近使用的文件列表
- 与用户操作相关的日志文件
- 示例:
~/.local/state/myapp/history.log
在以下情况下使用 $XDG_CACHE_HOME:
- 可安全删除的临时文件
- 可重新获取的下载数据
- 构建产物和编译文件
- 缩略图缓存
- 示例:
~/.cache/myapp/downloads/
在以下情况下使用 $XDG_RUNTIME_DIR:
- Unix 域套接字
- 命名管道(FIFO)
- 锁文件
- 临时 IPC 文件
- 警告:通常挂载为 tmpfs,且有大小限制;避免存放大文件
- 警告:注销时会清理;不要存放持久化数据
- 示例:
/run/user/1000/myapp/socket
Python 实现模式
仅使用标准库的实现
适用于仅面向 Linux/Unix 系统或不需要依赖项的应用。
"""XDG Base Directory compliant path management using stdlib only."""
from pathlib import Path
import os
def get_config_home() -> Path:
"""Get XDG_CONFIG_HOME path.
Returns user-specific configuration directory.
Falls back to $HOME/.config if XDG_CONFIG_HOME unset or relative.
"""
xdg = os.environ.get("XDG_CONFIG_HOME")
if xdg and Path(xdg).is_absolute():
return Path(xdg)
return Path.home() / ".config"
def get_data_home() -> Path:
"""Get XDG_DATA_HOME path.
Returns user-specific data directory.
Falls back to $HOME/.local/share if XDG_DATA_HOME unset or relative.
"""
xdg = os.environ.get("XDG_DATA_HOME")
if xdg and Path(xdg).is_absolute():
return Path(xdg)
return Path.home() / ".local" / "share"
def get_state_home() -> Path:
"""Get XDG_STATE_HOME path.
Returns user-specific state directory.
Falls back to $HOME/.local/state if XDG_STATE_HOME unset or relative.
"""
xdg = os.environ.get("XDG_STATE_HOME")
if xdg and Path(xdg).is_absolute():
return Path(xdg)
return Path.home() / ".local" / "state"
def get_cache_home() -> Path:
"""Get XDG_CACHE_HOME path.
Returns user-specific cache directory.
Falls back to $HOME/.cache if XDG_CACHE_HOME unset or relative.
"""
xdg = os.environ.get("XDG_CACHE_HOME")
if xdg and Path(xdg).is_absolute():
return Path(xdg)
return Path.home() / ".cache"
def get_runtime_dir() -> Path | None:
"""Get XDG_RUNTIME_DIR path.
Returns user-specific runtime directory, or None if not set.
This variable has no default - it must be set by the system.
"""
xdg = os.environ.get("XDG_RUNTIME_DIR")
if xdg and Path(xdg).is_absolute():
return Path(xdg)
return None
def get_config_dirs() -> list[Path]:
"""Get XDG_CONFIG_DIRS as list of paths.
Returns preference-ordered list of system configuration directories.
Falls back to [Path('/etc/xdg')] if XDG_CONFIG_DIRS unset.
"""
xdg = os.environ.get("XDG_CONFIG_DIRS")
if xdg:
paths = [Path(p) for p in xdg.split(":") if p and Path(p).is_absolute()]
if paths:
return paths
return [Path("/etc/xdg")]
def get_data_dirs() -> list[Path]:
"""Get XDG_DATA_DIRS as list of paths.
Returns preference-ordered list of system data directories.
Falls back to [Path('/usr/local/share'), Path('/usr/share')] if unset.
"""
xdg = os.environ.get("XDG_DATA_DIRS")
if xdg:
paths = [Path(p) for p in xdg.split(":") if p and Path(p).is_absolute()]
if paths:
return paths
return [Path("/usr/local/share"), Path("/usr/share")]
应用专用路径模块
为应用创建专用的路径模块:
"""Path management for myapp following XDG specification."""
from pathlib import Path
import os
APP_NAME = "myapp"
def get_config_dir() -> Path:
"""Get myapp config directory."""
xdg = os.environ.get("XDG_CONFIG_HOME")
base = Path(xdg) if xdg and Path(xdg).is_absolute() else Path.home() / ".config"
return base / APP_NAME
def get_config_file() -> Path:
"""Get myapp config file path."""
return get_config_dir() / "config.toml"
def get_data_dir() -> Path:
"""Get myapp data directory."""
xdg = os.environ.get("XDG_DATA_HOME")
base = Path(xdg) if xdg and Path(xdg).is_absolute() else Path.home() / ".local" / "share"
return base / APP_NAME
def get_cache_dir() -> Path:
"""Get myapp cache directory."""
xdg = os.environ.get("XDG_CACHE_HOME")
base = Path(xdg) if xdg and Path(xdg).is_absolute() else Path.home() / ".cache"
return base / APP_NAME
def get_state_dir() -> Path:
"""Get myapp state directory."""
xdg = os.environ.get("XDG_STATE_HOME")
base = Path(xdg) if xdg and Path(xdg).is_absolute() else Path.home() / ".local" / "state"
return base / APP_NAME
def ensure_directories() -> None:
"""Create all required directories with appropriate permissions."""
for directory in [get_config_dir(), get_data_dir(), get_cache_dir(), get_state_dir()]:
directory.mkdir(parents=True, exist_ok=True)
使用 platformdirs 的跨平台实现
对于面向 Linux、macOS 和 Windows 的应用,请使用 platformdirs 库:
"""Cross-platform path management using platformdirs."""
from platformdirs import user_config_dir, user_data_dir, user_cache_dir, user_state_dir
from pathlib import Path
APP_NAME = "myapp"
APP_AUTHOR = "myapp" # Used on Windows
# Get platform-appropriate directories
# Linux: Uses XDG specification
# macOS: Uses ~/Library/Application Support, ~/Library/Caches
# Windows: Uses %APPDATA%, %LOCALAPPDATA%
config_dir = Path(user_config_dir(APP_NAME, APP_AUTHOR))
data_dir = Path(user_data_dir(APP_NAME, APP_AUTHOR))
cache_dir = Path(user_cache_dir(APP_NAME, APP_AUTHOR))
state_dir = Path(user_state_dir(APP_NAME, APP_AUTHOR))
# Auto-create directories
config_dir = Path(user_config_dir(APP_NAME, APP_AUTHOR, ensure_exists=True))
平台专属路径:
| 平台 | 配置 | 数据 | 缓存 | 状态 |
|---|---|---|---|---|
| Linux | ~/.config/myapp | ~/.local/share/myapp | ~/.cache/myapp | ~/.local/state/myapp |
| macOS | ~/Library/Application Support/myapp | ~/Library/Application Support/myapp | ~/Library/Caches/myapp | ~/Library/Application Support/myapp |
| Windows | C:\Users\<user>\AppData\Local\myapp\myapp | C:\Users\<user>\AppData\Local\myapp\myapp | C:\Users\<user>\AppData\Local\myapp\myapp\Cache | C:\Users\<user>\AppData\Local\myapp\myapp |
何时使用 platformdirs:
- 应用面向多个操作系统
- 需要遵循平台原生惯例(macOS
~/Library、Windows%APPDATA%) - 希望自动创建目录,并使用
ensure_exists=True
何时仅使用标准库:
- 应用仅面向 Linux/Unix
- 尽量减少依赖项
- 需要完全控制路径解析逻辑
常见反模式
1. 使用 ~/.appname(旧式模式)
问题:违反 XDG 规范,并使主目录杂乱。
# ❌ WRONG
config_file = Path.home() / ".myapp" / "config.toml"
解决方案:使用 ~/.config/appname/
# ✅ CORRECT
def get_config_dir() -> Path:
xdg = os.environ.get("XDG_CONFIG_HOME")
base = Path(xdg) if xdg and Path(xdg).is_absolute() else Path.home() / ".config"
return base / "myapp"
config_file = get_config_dir() / "config.toml"
2. 忽略环境变量
问题:硬编码路径会阻止用户自定义。
# ❌ WRONG
config_dir = Path.home() / ".config" / "myapp"
解决方案:始终优先检查环境变量。
# ✅ CORRECT
xdg = os.environ.get("XDG_CONFIG_HOME")
base = Path(xdg) if xdg and Path(xdg).is_absolute() else Path.home() / ".config"
config_dir = base / "myapp"
3. 接受相对路径
问题:XDG 规范要求仅使用绝对路径。
# ❌ WRONG
xdg = os.environ.get("XDG_CONFIG_HOME", str(Path.home() / ".config"))
return Path(xdg) # Accepts relative paths
解决方案:验证路径是否为绝对路径;若为相对路径,则回退到默认值。
# ✅ CORRECT
xdg = os.environ.get("XDG_CONFIG_HOME")
if xdg and Path(xdg).is_absolute():
return Path(xdg)
return Path.home() / ".config" # Default for unset or relative
4. 未创建目录
问题:写入文件时未确保其父目录存在。
# ❌ WRONG
config_file = get_config_dir() / "config.toml"
config_file.write_text(data) # Fails if directory doesn't exist
解决方案:写入文件前创建目录。
# ✅ CORRECT
config_file = get_config_dir() / "config.toml"
config_file.parent.mkdir(parents=True, exist_ok=True)
config_file.write_text(data)
5. 将缓存存放在配置目录中
问题:将可重新生成的数据与配置混在一起。
# ❌ WRONG
cache_file = get_config_dir() / "download_cache.json"
解决方案:将 $XDG_CACHE_HOME 用于可重新生成的数据。
# ✅ CORRECT
cache_file = get_cache_dir() / "download_cache.json"
6. 运行时目录中的大型文件
问题:$XDG_RUNTIME_DIR 通常挂载为 tmpfs,且有大小限制。
# ❌ WRONG
large_file = get_runtime_dir() / "model.gguf" # May be 1GB+
解决方案:使用 $XDG_DATA_HOME 存放大型持久文件。
# ✅ CORRECT
large_file = get_data_dir() / "models" / "model.gguf"
7. 未处理未设置的 XDG_RUNTIME_DIR
问题:$XDG_RUNTIME_DIR 没有默认值。
# ❌ WRONG
runtime_dir = get_runtime_dir()
socket_path = runtime_dir / "socket" # Fails if None
解决方案:使用前检查是否为 None。
# ✅ CORRECT
runtime_dir = get_runtime_dir()
if runtime_dir is None:
raise RuntimeError("XDG_RUNTIME_DIR not set by system")
socket_path = runtime_dir / "socket"
测试 XDG 合规性
使用环境变量进行手动测试
# Test with custom XDG directories
export XDG_CONFIG_HOME=/tmp/test-config
export XDG_DATA_HOME=/tmp/test-data
export XDG_CACHE_HOME=/tmp/test-cache
export XDG_STATE_HOME=/tmp/test-state
# Run application
myapp --help
# Verify files are in correct locations
ls -la /tmp/test-config/myapp/
ls -la /tmp/test-data/myapp/
ls -la /tmp/test-cache/myapp/
ls -la /tmp/test-state/myapp/
# Test with unset variables (should use defaults)
unset XDG_CONFIG_HOME XDG_DATA_HOME XDG_CACHE_HOME XDG_STATE_HOME
myapp --help
ls -la ~/.config/myapp/
ls -la ~/.local/share/myapp/
ls -la ~/.cache/myapp/
ls -la ~/.local/state/myapp/
自动化测试
"""Test XDG compliance."""
import os
import tempfile
from pathlib import Path
import pytest
def test_xdg_config_home_override(monkeypatch):
"""XDG_CONFIG_HOME environment variable is respected."""
with tempfile.TemporaryDirectory() as tmpdir:
monkeypatch.setenv("XDG_CONFIG_HOME", tmpdir)
config_dir = get_config_dir()
assert config_dir == Path(tmpdir) / "myapp"
def test_xdg_config_home_default(monkeypatch):
"""Default used when XDG_CONFIG_HOME unset."""
monkeypatch.delenv("XDG_CONFIG_HOME", raising=False)
config_dir = get_config_dir()
assert config_dir == Path.home() / ".config" / "myapp"
def test_relative_path_ignored(monkeypatch):
"""Relative paths in XDG variables are ignored per specification."""
monkeypatch.setenv("XDG_CONFIG_HOME", "relative/path")
config_dir = get_config_dir()
# Should fall back to default, not use relative path
assert config_dir == Path.home() / ".config" / "myapp"
def test_empty_string_uses_default(monkeypatch):
"""Empty string in XDG variable uses default."""
monkeypatch.setenv("XDG_CONFIG_HOME", "")
config_dir = get_config_dir()
assert config_dir == Path.home() / ".config" / "myapp"
def test_runtime_dir_none_when_unset(monkeypatch):
"""XDG_RUNTIME_DIR returns None when unset (no default)."""
monkeypatch.delenv("XDG_RUNTIME_DIR", raising=False)
runtime_dir = get_runtime_dir()
assert runtime_dir is None
def test_config_dirs_search_path(monkeypatch):
"""XDG_CONFIG_DIRS parsed as colon-separated search path."""
monkeypatch.setenv("XDG_CONFIG_DIRS", "/etc/xdg:/opt/config")
dirs = get_config_dirs()
assert dirs == [Path("/etc/xdg"), Path("/opt/config")]
def test_data_dirs_ignores_relative_paths(monkeypatch):
"""XDG_DATA_DIRS filters out relative paths."""
monkeypatch.setenv("XDG_DATA_DIRS", "/usr/share:relative/path:/opt/data")
dirs = get_data_dirs()
assert dirs == [Path("/usr/share"), Path("/opt/data")]
目录结构示例
~/.config/myapp/ # XDG_CONFIG_HOME
config.toml # Main configuration
credentials.json # User credentials
~/.local/share/myapp/ # XDG_DATA_HOME
models/ # Downloaded models
model-v1.gguf
databases/
user.db
~/.local/state/myapp/ # XDG_STATE_HOME
history.log # Command history
recent-files.json # Recently used files
~/.local/bin/ # User binaries (convention)
myapp # Application binary
~/.cache/myapp/ # XDG_CACHE_HOME
downloads/ # Downloaded temporary files
build/ # Build artifacts
配置文件加载模式
对于支持 XDG 的 TOML 配置文件,请启用 toml-python 技能:
Skill(skill: "python-engineering:toml-python")
toml-python 技能提供了使用 tomllib(Python 3.11+)和 tomli(反向移植版本)解析 TOML 的全面指南,其中包括使用 Pydantic 模型进行验证。
相关技能
为相关功能启用以下技能:
toml-python- TOML 配置文件解析与验证python-engineering- 现代 Python 开发模式与最佳实践uv- Python 包和项目管理
参考资料
官方规范
- XDG Base Directory Specification v0.8 - 主要权威来源(访问于 2025-01-15)
- Freedesktop.org Specifications Index - freedesktop 的所有规范
- XDG Specs Git Repository - 源代码仓库
实现指南
- ArchWiki:XDG Base Directory - 全面的实现指南(访问于 2025-01-15)
- platformdirs Python Library - 跨平台 Python 实现(访问于 2025-01-15)
社区资源
- XDG Mailing List - 官方讨论论坛
- Freedesktop.org Wiki - 补充文档
核心原则
- 仅使用绝对路径:验证所有 XDG 路径均为绝对路径;忽略相对路径
- 环境变量优先:始终先检查 XDG 环境变量,再使用默认值
- 遵循规范:严格遵循 XDG Base Directory Specification v0.8
- 创建目录:写入文件前创建父目录
- 合理存储:根据数据类型使用正确的目录(config、data、cache 或 state)
- 运行时目录限制:避免在
$XDG_RUNTIME_DIR中存放大型文件(tmpfs 有大小限制) - 跨平台意识:使用 platformdirs 支持 macOS/Windows
- 搜索路径处理:正确解析以冒号分隔的搜索路径
- 运行时目录无默认值:
$XDG_RUNTIME_DIR没有默认值;检查是否为 None - 测试:通过覆盖环境变量验证 XDG 合规性