zudo-paper

WSL2 SSH/mosh環境でmacOSのpbcopyを使えるようにする(OSC 52)

Author: Takazudo | 作成: 2026/03/05

概要

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 BEL
  • ESC ]: 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 BEL

tmux用の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 on
  • set-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ではunameDarwinを返すのでifブロックはスキップされ、ネイティブのpbcopyがそのまま使われる。WSL2(Linux)ではunameLinuxを返すので、OSC 52版のpbcopy関数が定義される。

$TMUX環境変数でtmux内かどうかを判定して、パススルー形式を使い分けている。

既存のcpwdccbエイリアスは一切変更不要。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の設定が必要ということ。