mcpField
X
xdg-base-directory

xdg-base-directory

github.com/jamie-bitflight开发38 次安装
代码仓库

关于 xdg-base-directory

指导开发者在 CLI 工具及其他应用中遵循 XDG 基础目录规范,选择文件存储路径。内容包括环境变量处理、目录优先级,以及 Python 标准库和 platformdirs 实现方式。

主要功能

  1. 01配置、数据、状态与缓存目录指南
  2. 02绝对路径及默认值校验
  3. 03环境变量优先级与搜索路径
  4. 04Python 标准库和 platformdirs 实现模式
  5. 05XDG 合规性测试指南

适用场景

  1. 01为 CLI 工具选择文件存储路径
  2. 02迁移隐藏的用户目录文件夹
  3. 03支持自定义 XDG 环境变量
  4. 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
WindowsC:\Users\<user>\AppData\Local\myapp\myappC:\Users\<user>\AppData\Local\myapp\myappC:\Users\<user>\AppData\Local\myapp\myapp\CacheC:\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 包和项目管理

参考资料

官方规范
实现指南
社区资源

核心原则

  1. 仅使用绝对路径:验证所有 XDG 路径均为绝对路径;忽略相对路径
  2. 环境变量优先:始终先检查 XDG 环境变量,再使用默认值
  3. 遵循规范:严格遵循 XDG Base Directory Specification v0.8
  4. 创建目录:写入文件前创建父目录
  5. 合理存储:根据数据类型使用正确的目录(config、data、cache 或 state)
  6. 运行时目录限制:避免在 $XDG_RUNTIME_DIR 中存放大型文件(tmpfs 有大小限制)
  7. 跨平台意识:使用 platformdirs 支持 macOS/Windows
  8. 搜索路径处理:正确解析以冒号分隔的搜索路径
  9. 运行时目录无默认值:$XDG_RUNTIME_DIR 没有默认值;检查是否为 None
  10. 测试:通过覆盖环境变量验证 XDG 合规性

评论

支持 Markdown