xdg-base-directory について
CLI ツールなどのアプリケーションで XDG Base Directory Specification に沿ったパスを設計するためのガイドです。環境変数の扱いやディレクトリの優先順位、Python の実装パターンを説明します。
主な機能
- 01設定、データ、状態、キャッシュのパス指針
- 02絶対パスと既定値の検証
- 03環境変数の優先順位と検索パス
- 04Python 標準ライブラリと platformdirs の実装例
- 05XDG 準拠のテスト指針
活用シーン
- 01CLI ツールのファイル保存先の選定
- 02ホームディレクトリの隠しフォルダーからの移行
- 03XDG 環境変数のカスタム設定への対応
- 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 に厳密に従います
- ディレクトリの作成:ファイルを書き込む前に親ディレクトリを作成します
- 適切な保存先:データの種類(設定、データ、キャッシュ、状態)に応じて正しいディレクトリを使用します
- ランタイムディレクトリの制限:
$XDG_RUNTIME_DIRに大きなファイルを置かないようにします(tmpfs の制限) - クロスプラットフォームへの配慮:macOS/Windows のサポートには platformdirs を使用します
- 検索パスの処理:コロン区切りの検索パスを正しく解析します
- ランタイムのデフォルト値なし:
$XDG_RUNTIME_DIRにデフォルト値はありません。None かどうかを確認します - テスト:環境変数の上書きを使って XDG への準拠を検証します