概要
SSH/mosh経由でWSL2に接続した環境で、macOSのpbcopyと同じようにクリップボードコピーを実現する方法についてのメモ。OSC 52エスケープシーケンスを使って、tmux内でも動作するユニバーサルな設定を作った話。
macOSではpbcopyコマンドでパイプからクリップボードにコピーできる。.zshrcに以下のようなエイリアスを定義して便利に使っていた。
alias cpwd='pwd | pbcopy'
alias ccb='git branch --show-current | pbcopy'macOSとWSL2で同じ.zshrcを共有しているので、WSL2側でも同じように動く仕組みが必要になった。いくつか試して最終的にOSC 52に落ち着いたので、そのまとめ。
最初に試したこと: clip.exe
WSL2にはWindowsのclip.exeが使えるという情報がある。
alias pbcopy='clip.exe'ただ、これはWSL2のターミナルを直接使っている場合にしか動かない。SSH/mosh経由で接続した場合、WSL interopレイヤーが有効でないためclip.exeにアクセスできずcommand not foundになる。自分の環境はSSH/moshで接続しているので、これは使えなかった。
xclip / xselも使えない
Xサーバーが必要で、ヘッドレスなSSHセッションでは動かない。却下。
OSC 52エスケープシーケンス
最終的にたどり着いたのがOSC 52。これはターミナルエミュレータにクリップボードへのコピーを指示するエスケープシーケンスで、SSH/moshを通じてローカルのターミナルエミュレータのクリップボードに直接書き込める。
OSC 52のフォーマット
ESC ] 52 ; c ; BASE64_DATA BELESC ]: OSC(Operating System Command)の開始52: クリップボード操作のコードc: システムクリップボードを指定BASE64_DATA: コピーしたいテキストをBase64エンコードしたものBEL(\a)または ST(ESC \): シーケンスの終端
仕組みとしては、このエスケープシーケンスがSSH/moshの接続を通じてローカル側のターミナルエミュレータに届き、ターミナルがクリップボードにデータを書き込む。リモート側のOSのクリップボード機能に依存しないので、SSH越しでも動く。
対応ターミナル
- Ghostty: デフォルトで対応
- Windows Terminal: デフォルトで対応
- iTerm2: 設定で有効化が必要(Settings > General > Selection > "Applications in terminal may access clipboard")
- Alacritty: デフォルトで対応
moshは1.4.0以降でOSC 52をサポートしている。
zshでの関数定義でエラーになる
OSC 52を使ってpbcopy関数を定義しようとしたところ、zshでエラーになった。
/home/user/.zshrc:406: defining function based on alias `pbcopy'
/home/user/.zshrc:406: parse error near `()'macOS環境ではpbcopyはシステムコマンドとして存在するが、他の環境でpbcopyをaliasとして定義していたり、あるいはプラグインが先にalias登録しているケースがある。zshでは既存のaliasと同名の関数をname() { ... }構文で定義しようとするとエラーになる。
解決策はfunctionキーワードを付けること。
# NG: aliasと同名でエラー
pbcopy() {
...
}
# OK: functionキーワードでaliasの展開を抑制
function pbcopy() {
...
}function name() { ... }構文を使うと、zshはalias展開を行わずに関数定義として処理してくれる。
tmux内で動作しない問題
OSC 52はそのままではtmuxを通過できない。tmux内では「DCSパススルー」でエスケープシーケンスをラップする必要がある。
通常のOSC 52:
ESC ] 52 ; c ; BASE64_DATA BELtmux用のDCSパススルー形式:
ESC P tmux; ESC ESC ] 52 ; c ; BASE64_DATA BEL ESC \ESCが二重になっているのは、tmuxがDCS内のエスケープを1段アンエスケープするため。
さらに.tmux.confにも設定が必要。
set -g set-clipboard on
set -g allow-passthrough onset-clipboard on: tmuxがOSC 52を処理できるようにするallow-passthrough on: エスケープシーケンスがtmuxを通過してターミナルエミュレータに届くようにする
最終的な設定
.zshrc(macOSとWSL2で共有)
# On non-macOS, define pbcopy using OSC 52 escape sequence
# Works over SSH/mosh if the terminal supports OSC 52 (most modern terminals do)
if [[ "$(uname)" != "Darwin" ]]; then
function pbcopy() {
local data=$(cat)
local encoded=$(echo -n "$data" | base64 | tr -d '\n')
if [[ -n "$TMUX" ]]; then
printf "\ePtmux;\e\e]52;c;%s\a\e\\" "$encoded"
else
printf "\e]52;c;%s\e\\" "$encoded"
fi
}
fi
alias cpwd='pwd | pbcopy'
alias ccb='git branch --show-current | pbcopy'macOSではunameがDarwinを返すのでifブロックはスキップされ、ネイティブのpbcopyがそのまま使われる。WSL2(Linux)ではunameがLinuxを返すので、OSC 52版のpbcopy関数が定義される。
$TMUX環境変数でtmux内かどうかを判定して、パススルー形式を使い分けている。
既存のcpwdやccbエイリアスは一切変更不要。pbcopyコマンド/関数の中身が環境に応じて切り替わるだけなので、それを使っているエイリアスはそのまま動く。
.tmux.conf
set-window-option -g mode-keys vi
set -g set-clipboard on
set -g allow-passthrough onまとめ
OSC 52を使えば、SSH/mosh経由のリモートセッションでもローカルのクリップボードにコピーできる。macOSとWSL2で同じ.zshrcを共有しつつ、unameによる分岐だけでユニバーサルに動作する設定が実現できた。
試した選択肢を整理すると以下。
clip.exe: SSH/mosh経由では使えない(WSL interopが無効)xclip/xsel: Xサーバーが必要でヘッドレスSSHセッションでは使えない- OSC 52: SSH/moshを通じてローカルのターミナルに届くので、リモート環境に依存しない
注意する点としては、zshでfunctionキーワードを付けないとalias衝突でエラーになることと、tmux内ではDCSパススルーと.tmux.confの設定が必要ということ。