Mac と Ubuntu で同じ開発環境を作る dotfiles 設計メモ 第2回:Ghostty・zsh・tmux の OS 差分を吸収する
はじめに
前回は、Mac と Ubuntu で同じ開発環境を作るための dotfiles 全体設計と、公開時の注意点について書きました。
今回は、実際の設定例として、次の3つを扱います。
- Ghostty
- zsh
- tmux
いずれも、Mac と Ubuntu の違いが出やすい部分です。
特に、次のような差分をどう吸収するかがテーマです。
- 設定ファイルの読み分け
- Homebrew と apt で異なるファイル配置
- macOS と Ubuntu X11 で異なるクリップボード連携
検証環境
この記事の内容は、以下のような環境を前提にしています。
| 項目 | 環境 |
|---|---|
| macOS | TODO: 例 macOS 15.x
|
| Ubuntu | TODO: 例 Ubuntu 24.04 LTS
|
| Ubuntu のセッション | X11 |
| シェル | zsh |
| ターミナル | Ghostty |
| ターミナルマルチプレクサ | tmux |
| Linux 側のクリップボード連携 | xsel |
Ubuntu 側は X11 前提です。
X11 / Wayland の確認は、次のコマンドでできます。
echo "$XDG_SESSION_TYPE"
この記事では、Ubuntu 側で x11 が返る環境を想定しています。
設計方針
複数 OS で dotfiles を使い回すときの基本方針は、次の通りです。
- 共通設定は 1 か所に寄せる
- OS 固有の設定だけを分離する
- パスやコマンドの違いは小さな関数や条件分岐で吸収する
- できるだけ同じ手順で展開できるようにする
今回は、この方針を Ghostty / zsh / tmux に適用していきます。
Ghostty:設定ファイルを階層的に読み込む
Ghostty は config-file を使って、別の設定ファイルを読み込めます。
これを利用して、共通設定と OS 固有設定を分けています。
# config.ghostty
config-file = ?base
config-file = ?linux
config-file = ?macos
base には共通設定を書きます。
# base
font-size = 14
theme = catppuccin-mocha
confirm-close-surface = false
Linux 固有の設定は linux に書きます。
# linux
mouse-hide-while-typing = true
macOS 固有の設定は macos に書きます。
# macos
macos-titlebar-style = transparent
window-colorspace = "display-p3"
この構成にすると、共通設定を base に置きつつ、OS ごとの差分だけを別ファイルに逃がせます。
?linux / ?macos は OS 判定ではない
ここで注意点があります。
Ghostty の ?linux や ?macos は、OS を自動判定しているわけではありません。
? は「ファイルが存在しなければ無視する」という指定です。
つまり、同じ設定ディレクトリに linux と macos の両方が存在すると、両方とも読み込まれます。
そのため、この構成は次のどちらかを前提にしています。
- OS ごとに必要なファイルだけを配置する
- インストールスクリプトや symlink で、OS ごとに読み込むファイルを切り替える
自分の dotfiles では、OS 固有設定が混ざらないように、展開時にどのファイルを置くかを意識しています。
単に「設定を分ける」だけでなく、その設定ファイルがいつ・どこで・どう読まれるかを理解しておく必要があると感じました。
zsh:プラグインの場所を OS に応じて探索する
zsh-autosuggestions のような zsh プラグインは、インストール方法によって置かれる場所が異なります。
例えば、Mac の Homebrew で入れた場合と、Ubuntu のパッケージで入れた場合では、配置先が変わります。
# Homebrew の例
$HOMEBREW_PREFIX/share/zsh-autosuggestions/zsh-autosuggestions.zsh
# Ubuntu パッケージの例
/usr/share/zsh-autosuggestions/zsh-autosuggestions.zsh
そのため、固定パスで source すると、片方の OS で壊れます。
そこで、候補のパスを順番に探して、見つかったものを返す関数を用意しました。
detect_plugin_path() {
local plugin_dir="$1"
local brew_prefix="${HOMEBREW_PREFIX:-}"
if [[ -z "$brew_prefix" ]] && command -v brew >/dev/null 2>&1; then
brew_prefix="$(brew --prefix 2>/dev/null)"
fi
if [[ -n "$brew_prefix" && -r "$brew_prefix/share/$plugin_dir/$plugin_dir.zsh" ]]; then
echo "$brew_prefix/share/$plugin_dir/$plugin_dir.zsh"
elif [[ -r "/usr/share/$plugin_dir/$plugin_dir.zsh" ]]; then
echo "/usr/share/$plugin_dir/$plugin_dir.zsh"
elif [[ -r "/usr/local/share/$plugin_dir/$plugin_dir.zsh" ]]; then
echo "/usr/local/share/$plugin_dir/$plugin_dir.zsh"
fi
}
使う側では、空文字の場合に source しないようにしています。
plugin_path="$(detect_plugin_path zsh-autosuggestions)"
if [[ -n "$plugin_path" ]]; then
source "$plugin_path"
else
echo "zsh-autosuggestions not found" >&2
fi
$HOMEBREW_PREFIX が未設定の場合も考える
最初は $HOMEBREW_PREFIX がある前提で書いていました。
しかし、環境によっては $HOMEBREW_PREFIX が未設定の場合があります。
そのため、未設定の場合は brew --prefix で取得するようにしています。
if [[ -z "$brew_prefix" ]] && command -v brew >/dev/null 2>&1; then
brew_prefix="$(brew --prefix 2>/dev/null)"
fi
これにより、Apple Silicon Mac、Intel Mac、Linuxbrew などの差分をある程度吸収できます。
この関数を書くために、次のようなことを調べることになりました。
- Homebrew の prefix はどこか
- Apple Silicon Mac と Intel Mac で Homebrew の場所が違うこと
- Linux では
/usr/shareと/usr/local/shareの意味が違うこと - パッケージ管理経由で入れたファイルがどこに置かれるか
設定ファイルを共通化したいという動機が、そのまま Linux のファイルシステムの理解につながった例です。
tmux:OS 判定でクリップボード連携を切り替える
tmux のコピーモードでヤンクした内容を OS のクリップボードに渡す部分も、OS ごとに使うコマンドが異なります。
macOS では pbcopy を使えます。
一方、今回の Ubuntu 環境は X11 なので、Linux 側では xsel を使っています。
if-shell "uname | grep -q Darwin" \
'bind-key -T copy-mode-vi y send-keys -X copy-pipe-and-cancel "pbcopy"' \
'bind-key -T copy-mode-vi y send-keys -X copy-pipe-and-cancel "xsel -bi"'
Ubuntu 側では、事前に xsel を入れておきます。
sudo apt install xsel
この設定を書く前に、次の確認をしました。
echo "$XDG_SESSION_TYPE"
command -v xsel
今回の Ubuntu 環境では XDG_SESSION_TYPE が x11 なので、xsel を使う構成にしています。
Wayland や SSH では別の方法が必要
この設定は、すべての Linux 環境でそのまま動くわけではありません。
Wayland 環境では、xsel ではなく wl-copy を使う方が自然です。
# Wayland の例
bind-key -T copy-mode-vi y send-keys -X copy-pipe-and-cancel "wl-copy"
また、SSH 越しの tmux では、ローカルマシンのクリップボードに直接渡せない場合があります。
その場合は OSC 52 など、別の方法を検討する必要があります。
今回の記事では Ubuntu X11 環境に絞っているため、xsel を使う構成にしています。
クリップボード連携で理解したこと
この設定を書くまで、クリップボードは何となくエディタや tmux が持っているものだと思っていました。
しかし実際には、tmux の copy-mode、ターミナルエミュレータ、OS のクリップボード、X11 / Wayland などが関係しています。
つまり、クリップボード連携は次のような橋渡しです。
tmux copy-mode
-> copy-pipe-and-cancel
-> pbcopy / xsel / wl-copy
-> OS のクリップボード
この構造を理解すると、なぜ OS やセッションによって設定が変わるのかが分かりやすくなりました。
今回のまとめ
今回は、Ghostty / zsh / tmux を例に、Mac と Ubuntu の差分をどう吸収しているかを書きました。
やったことをまとめると、次の通りです。
- Ghostty は共通設定と OS 固有設定をファイル分割した
-
?linux/?macosは OS 判定ではなく、存在しないファイルを無視する指定だと理解した - zsh プラグインは Homebrew / apt の配置差分を関数で吸収した
-
$HOMEBREW_PREFIXが未設定の場合も考慮した - tmux のクリップボード連携は macOS では
pbcopy、Ubuntu X11 ではxselを使った - Wayland や SSH では別の方法が必要だと分かった
設定ファイルを共通化する作業は、単に便利にするだけでなく、OS ごとの違いを理解するきっかけにもなります。
次回
次回は、Neovim の設定を扱います。
具体的には、次の内容を書きます。
- Lua による Neovim 設定の分割
- VS Code Neovim との両対応
- lazy.nvim と
lazy-lock.jsonによる再現性 - 日本語入力 IME が Esc 後も残る問題への対応