홈 디렉터리를 깔끔하게 유지하기 위해 XDG 기본 디렉터리 명세를 따르는 이유와 방법, 코드 예시와 FAQ를 설명합니다.
당신의 홈 디렉터리를 한번 보세요
$ ls -FA1 ~
'Universe Sandbox'/
.alsoftrc
.android/
.ansible/
.aqbanking/
.audacity-data/
.bashrc
.cache/
.choosenim/
.code-d/
.config/
.cpan/
.eclipse/
.elementary/
.emacs.d/
.emulator_console_auth_token
.flutter
.flutter_tool_state
.gem/
.ghc/
.ghidra/
.gnome/
.gnupg/
.godot/
.gore/
.gradle/
.gsutil/
.guestfish
.heekscad
.helioslauncher/
.hushlogin
.idapro/
.ivy2/
.java/
.kb/
.kube/
.lldb/
.local/
.lunarclient/
.lyxauth
.m2/
.mcfly/
.metals/
.minecraft/
.mono/
.mozilla
.mputils/
.mume/
.omnisharp/
.ort/
.osc_cookiejar
.pack/
.paradoxlauncher/
.parsec/
.pki/
.pm2/
.profile
.pythonhist
.sbt/
.scim/
.ssh/
.steam/
.steampath
.steampid
.step/
.subversion/
.swt/
.tooling/
.vscode/
.w3m/
.wine/
.xinitrc
.yarnrc
.zcompdump
.zoom/
.zshenv
Desktop/
Documents/
Downloads/
HomeBank-20210521.bak
HomeBank-20210607.bak
HomeBank-20210611.bak
Music/
Pictures/
Videos/
config/
data/
javasharedresources/
macOS.vdi
집(홈 디렉터리)이 깔끔하고 정돈되어 있다면 어떨까요?
$ ls -FA1 ~
.bashrc
.cache/
.config/
.local/
.profile
Desktop/
Documents/
Downloads/
Music/
Pictures/
Videos/
당신의 애플리케이션의 최종 사용자(그리고 바라는 마음으로 당신 자신도)에게는 깨끗한 홈 디렉터리가 필요합니다. ~/.gitconfig 같은 파일들이 홈 폴더 여기저기에 난잡하게 흩어져 있기보다는, 전용 설정 디렉터리에 모여 있는 것이 훨씬 좋습니다.
저는 0x46.net의 Filip이 사용자 관점에서 이 문제를 설명한 방식이 마음에 듭니다:
제 홈 디렉터리에는 평범한 파일이 25개, 숨김 파일이 144개 있습니다. 이 도트파일들은 제 것이 아닌 데이터를 담고 있습니다: 그것은 제 개인 파일을 저장하도록 설계된 주요 위치를 빼앗아 버린 프로그램들의 개발자들에게 속한 데이터입니다. 저는 그 도트파일들을 다른 곳에 둘 수 없고, 지우려고 해도 다시 나타납니다. 제가 할 수 있는 일이라고는 어둠 속, 무대 뒤에서 그것들이 거기 있다는 사실을 알며 앉아 있는 것뿐입니다. ... 어느 날 문을 거세게 두드리며 개발자 중 하나가 들이닥쳐 제 거실 한가운데에 자기 가구를 놓겠다고 말하는 소리를 듣게 될까 봐 두렵습니다. 제가 상관없다면요.
0x46.net의 Filip
간단히 말해, 이 명세를 따르면(최소한) 당신의 애플리케이션 파일들이 홈 폴더 하위의 하위 디렉터리들로 거의 분류됩니다. 예를 들어, 캐시 파일의 기본 위치는 ~/.cache, 설정 파일의 기본 위치는 ~/.config 등입니다.
깨끗한 홈 디렉터리에 이미 설득되지 않았다면, 추가적인 장점들이 있습니다.
여러 디렉터리(예: 기본값인 ~/.config 등)가 특정 종류의 애플리케이션 파일을 대표하므로, 백업 시 특정 범주의 파일에 대한 규칙을 만들기가 훨씬 쉽습니다.
모든 설정이 단일 디렉터리에 있기 때문에, 서로 다른 컴퓨터 간에 더 쉽게 공유할 수 있습니다.
단일 머신에 특화된 모든 데이터가 단일 디렉터리에 있으므로, 데이터를 공유하거나 백업할 때 시스템 간에 그것이 섞이지 않도록 쉽게 피할 수 있습니다.
모든 애플리케이션이 --no-config 옵션을 제공하는 것은 아닙니다(특히 GUI 애플리케이션). 그리고 --config 옵션이 있더라도, /dev/null 파일, 셸 프로세스 치환, 또는 빈 파일을 사용할 때 항상 잘 동작하는 것은 아닙니다. XDG_CONFIG_HOME=/tmp/dir로 설정하는 것이 가장 쉽습니다.
애플리케이션이 $HOME에 직접 파일을 쓰는 경우, 시스템 접근을 제한하기가 어렵습니다. 모든 디렉터리가 특정 애플리케이션에 특화되어 있으므로 접근 제어 패턴을 구현하기가 더 쉽습니다.
좀 더 구체적인 예로, /etc가 존재하지 않는다고 상상해 보세요 — 설정이 오늘날 $HOME이 그렇듯이 난잡하게 흩어져 있을 것입니다. 하지만 파일 계층 구조 표준이 설정 파일(과 다른 디렉터리들)을 위한 디렉터리로 /etc를 명확히 지정해 두었기 때문에, 우리는 머신을 넘어 시스템 파일을 찾고, 편집하고, 백업하기가 엄청나게 쉬워졌습니다. 이러한 사용성은 시스템 수준 파일에만 존재해서는 안 되며, 사용자 수준에서도 마찬가지여야 합니다.
그러니, 모든 애플리케이션 개발자 여러분께 간청합니다, 저는 정말로 부탁드립니다. XDG 기본 디렉터리 명세를 구현해 주세요.
먼저, 프로그램이 써야 하는 파일을 네 가지 범주로 분류하세요:
프로그램 동작에 영향을 주는 설정 파일. 설정 파일이 사람이 편집하도록 설계되지 않았더라도, 대개 이 범주에 속합니다.
본질적으로 컴퓨터 간에 이식 가능한 일반 파일과 데이터. 예로는 폰트, 인터넷에서 다운로드한 파일, [데스크톱 항목](https://specifications.freedesktop.org/desktop-entry-spec/desktop-entry-spec-latest.html) 등이 있습니다.
애플리케이션의 상태를 보관하는 파일. 로그, 명령 기록, 최근에 사용한 파일 목록, 게임 저장 데이터 등을 포함할 수 있습니다. 다시 말해, 특정 머신에 고유한 데이터라면 이 범주에 속합니다.
캐시 파일.
위의 각 범주는 특별한 환경 변수에 매핑되며, 그 변수는 해당 범주의 모든 파일을 담는 디렉터리를 가리킵니다. 예를 들어, "설정" 파일은 모두 $XDG_CONFIG_HOME에 저장됩니다. 그 환경 변수가 올바르지 않다면, 기본값인 $HOME/.config을(를) 대신 사용해야 합니다. 전체 표는 아래와 같습니다:
| 범주 | 환경 변수 | 기본값 | FHS 대략 대응 |
|---|---|---|---|
| 설정 | $XDG_CONFIG_HOME | $HOME/.config | /etc |
| 데이터 | $XDG_DATA_HOME | $HOME/.local/share | /usr/share |
| 상태 | $XDG_STATE_HOME | $HOME/.local/state | /var/lib |
| 캐시 | $XDG_CACHE_HOME | $HOME/.cache | /var/cache |
환경 변수가 올바르지 않은 경우는 세 가지입니다:
unset XDG_CONFIG_HOME)XDG_CONFIG_HOME="")XDG_CONFIG_HOME="./config")이제 표준에 익숙해졌다면, 코드 예시를 보고 싶을 겁니다. 다음 프로그램들은 오직 Linux 전용입니다(아래 FAQ에서 macOS와 Windows에 대한 내용을 참조하세요). 물론 실제로 프로그램을 작성한다면 라이브러리를 사용하는 것을 권장합니다.
다음은 Go 1.13+ 코드입니다. os.UserConfigDir (소스)는 명세에 따라 절대 경로가 아닌 값을 조용히 무시하지 않기 때문에 사용하지 않았습니다.
Go 코드 보기
package main
import (
"os"
"fmt"
"log"
"path/filepath"
)
func getConfigDir() (string, error) {
configDir := os.Getenv("XDG_CONFIG_HOME")
// 환경 변수 값이 설정되지 않았거나 비어 있거나 절대 경로가 아니라면 기본값을 사용
if configDir == "" || configDir[0:1] != "/" {
homeDir, err := os.UserHomeDir()
if err != nil {
return "", err
}
return filepath.Join(homeDir, ".config", "my-application-name"), nil
}
// 환경 변수 값이 유효하므로 그대로 사용
return filepath.Join(configDir, "my-application-name"), nil
}
func main() {
config_dir, err := getConfigDir()
if err != nil {
panic(err)
}
fmt.Println(config_dir)
}
다음은 Python 3.5+ 코드입니다. Path.home (소스)를 사용합니다(이는 os.path.expanduser (소스)를 사용합니다)
Python 코드 보기
import sys, os
from pathlib import Path
def get_config_dir() -> str:
config_dir = os.getenv('XDG_CONFIG_HOME', '')
// 환경 변수 값이 설정되지 않았거나 비어 있거나 절대 경로가 아니라면 기본값을 사용
if config_dir == '' or config_dir[0] != '/':
return str(Path.home().joinpath('.config', 'my-application-name'))
// 환경 변수 값이 유효하므로 그대로 사용
return str(Path(config_dir).joinpath('my-application-name'))
config_dir = get_config_dir()
print(config_dir)
다음은 Bash 코드입니다. 명세에 따라 절대 경로가 아닌 값을 조용히 무시하지 않기 때문에 "${XDG_CONFIG_HOME:-$HOME/.config}"는 사용하지 않았습니다.
Bash 코드 보기
get_config_dir() {
unset REPLY; REPLY=
local config_dir="${XDG_CONFIG_HOME-}"
# 환경 변수 값이 설정되지 않았거나 비어 있거나 절대 경로가 아니라면 기본값을 사용
if [ -z "$config_dir" ] || [ "${config_dir::1}" != '/' ]; then
REPLY="$HOME/.config/my-application-name"
return
fi
# 환경 변수 값이 유효하므로 그대로 사용
REPLY="$config_dir/my-application-name"
}
get_config_dir
printf '%s\n' "$REPLY"
표준에는 더 많은 내용이 있지만, 위에서 설명한, 가장 중요한 부분만이라도 최소한 알고 구현해야 합니다. 이걸 직접 구현하고 싶지 않다면 라이브러리를사용하세요. 이렇게 구현하면 사용자는 조용히 당신에게 감사할 것입니다!
$HOME의 몇 개 개별 파일에만 쓰는 경우라면, XDG 기본 디렉터리 명세를 따르기 전에 우선 그 파일들이 존재하는지 확인하세요. 예를 들어 기존 설정 위치가 ~/foo-app.json이라고 합시다: ~/foo-app.json이 존재하면 그걸 쓰고, 존재하지 않는다면 $XDG_CONFIG_HOME이 설정돼 있고 유효한지 확인하세요. 유효하다면 $XDG_CONFIG_HOME/foo-app/foo-app.json에 쓰고, 아니라면 $HOME/.config/foo-app/foo-app.json에 씁니다.
하지만 설정과 데이터를 모두 홈의 하위 디렉터리(예: ~/.ansible)에 담는 경우라면, 상황이 그리 간단하지 않습니다. 전부를 $XDG_STATE_HOME/application-dir로 옮기는 것이 애플리케이션 개발자에게는 가장 쉽겠지만, 의미론적으로는 덜 올바릅니다. 여러 파일을 서로 다른 디렉터리로 마이그레이션하는 문제를 걱정하고 싶지 않다면, 우선 환경 변수로 그 디렉터리의 위치를 설정 가능하게 만드는 것부터 시작해도 좋습니다. 예를 들어 Terraform은 TF_DATA_DIR을 통해 이 옵션을 제공합니다. 디렉터리를 환경 변수로 노출하기만 해도 놀라울 정도로 유용합니다.
Kitty FontForge ImageMagick Alacritty Composer Terminator clangd chezmoi aria2 bat i3 nano picom poetry VLC awesome aerc micro Handbrake OfflineIMAP polybar rclone xmonad mesa Godot Docker Anki httpie citra basher asunder Transmission htop Termite Git Kakoune Blender Gstreamer ranger Pry TypeScript navi d-feet Mercurial LibreOffice Audacious byobu colordiff cmus ccache antimicro lftp mc calcurse Deluge Terraria Wireshark sway xsettingsd tmux PulseAudio neomutt VirtualBox broot httpie ALSA pandoc nb tig cargo openbox asdf fish fontconfig Dolphin MultiMC pnpm GIMP binwalk Inkscape iwd mpv josm Wechat
더 긴 목록과 자세한 내용은 Arch 위키를 참고하세요.
--config 옵션을 제공합니다. 정말 이게 필요할까요?물론입니다! 명령줄 인수로 파일이나 디렉터리를 지정하는 능력은 일관되게 사용될 수 없습니다. 예를 들어 셸 에일리어스를 정의할 수는 있지만, 비대화식 환경에서는 Bash 옵션 expand_aliases가 기본적으로 꺼져 있기 때문에 항상 사용되지는 않습니다. 게다가 프로그램이 어떤 프로그래밍적 방식(예: exec 계열 시스템 호출)으로 호출되는 경우, 해당 플래그가 전달되도록 보장할 합리적인 방법이 없습니다.
꽤 조사해 보았지만, 공통된 합의는 없어 보입니다. 일반적으로, 프로그램의 유일한 인터페이스가 CLI라면 Linux가 아니더라도 XDG 기본 디렉터리 명세를 따르는 것이 허용됩니다. 이는 생태계 전반, 특히 Bash 프로그램에서 꽤 흔합니다. 반면에 GUI라면 일반적으로 macOS 디렉터리 위치 표준을 따릅니다. 예를 들어 Sublime Text 3는 macOS에서 설정을 ~/Library/Application Support/Sublime Text 3/Packages/User에 저장합니다. 비록 subl 명령을 함께 제공하더라도 말이죠.
단언하기 어렵습니다. 제가 Windows를 충분히 자주 사용하지 않기 때문입니다. Windows에는 이미 사용자 데이터를 저장할 기존 디렉터리가 있다는 점을 덧붙이겠습니다. 그럼에도, scoop 같은 널리 사용되는 애플리케이션은 설정에 대해 이 명세를 따릅니다. 또 다른 커맨드라인 앱인 PSKoans는 ~/.config를 하드코딩합니다. 솔직히 말해, 그 사용성은 macOS와 비교하면 널리 보급되어 있지는 않으므로, 답은 "아니오"에 더 가깝습니다.
저는 Edwin Kofler입니다. 이 명세에 대한 지식(과 구현)이 더 넓은 개발자들에게 퍼지기를 바라는 소프트웨어 개발자죠. Poetry와 pnpm 같은 저장소에(이제는 수정된) 이슈를 열었고, osc, vscode-kubernetes-tools, mume, basher 등(그리고 더 많은 곳)에(이제는 머지된) PR을 제안했습니다! 함께 힘을 모아 홈 디렉터리를 다시 깔끔하게 만들어 봅시다!