tmuxの中のsshの先のtmuxの中の…から一気にクリップボードにコピーする方法
ターミナルで作業をしていて、ログなどをコピーしたいことはよくあります。ターミナル内で領域を選択してコピーすることもできますが、マウスで選択するのがやりづらかったり、画面に収まらない長さだとちょっと面倒です。
ローカルな環境で作業している場合は、Linux であれば xclip や wl-copy などを使って解決できることもありますが、ssh で入った先だったり、tmux の中だったり、tmux の中の ssh の先の tmux の中だったり、さらにその中のエディタ内だったり、実際の環境は多様で複雑です。
そんなときでも簡単にターミナルからコピーする方法、関連してターミナルに安全にペーストする方法を紹介します。
ターミナルエミューレーターを用意する
今回紹介する方法は、すべて OSC 52 という仕組みに依存します。OSC (Operating System Command) はターミナルで使われるエスケープシーケンスの種類の一つで、その中の OSC 52 は「ターミナル上に流れてきた特定のシーケンスをクリップボードに保存する」機能を提供します。
他にはウィンドウタイトルを変更する OSC 0 があったり、ターミナル上で色を変更するのも OSC ではないですがエスケープシーケンスによる機能です。
参考: https://en.wikipedia.org/wiki/ANSI_escape_code
筆者は foot というターミナルエミュレーターを使用していますが、alacritty, kitty, wezterm などのターミナルであればどれも OSC 52 をサポートしてます。
新しいターミナルウィンドウを開いたら、以下のコマンドを実行してみましょう。
$ printf "\e]52;c;%s\a" "$(echo "copy test" | base64 -w 0)"
実行後に、ブラウザなどでペーストしてみてください。"copy test" という文字列がペーストされていれば成功です。ペーストされない場合はターミナルが OSC 52 をサポートしていない可能性があります。ターミナルの設定で OSC 52 を有効にできるケースもありますが、うまくいかない場合はターミナルを変更してみてください。
tmux を起動する
次に tmux を起動してみましょう。新しめの tmux であれば OSC 52 に対応していますので、特に何も設定しなくても大丈夫です。
tmux でコピーモードに入って(デフォルトでは C-b [
)、領域を選択してコピーしてみてください(<Space>
で始点を選んで終点で <Enter>
)。
そしておもむろにブラウザかどこかにペーストすると、コピーしたテキストが出力されるはずです。
さて、ここで改めて以下を実行します。
$ printf "\e]52;c;%s\a" "$(echo "copy test" | base64 -w 0)"
ブラウザにペーストすると、"copy test" ではなく前回コピーした内容が挿入されると思います。クリップボードの中身がうまく上書きできていないようです。
これは tmux がデフォルトでは自分がコピーしたものしか OSC 52 として出力しないようになっているためです。
下記の設定を tmux.conf に加えて、tmux を再起動してみましょう。
set -g set-clipboard on
改めて
$ printf "\e]52;c;%s\a" "$(echo "copy test" | base64 -w 0)"
を実行すると "copy test" がペーストできるようになるはずです。
勘の良い方ならお気づきかと思いますが、これで tmux 内で ssh した先の tmux の中でコピーしたものでも、全て透過して手元のクリップボードが更新できます。ネストは何段あっても大丈夫です。
ペーストを安全にする
ターミナルエミュレーターによっては OSC 52 をサポートしていてもデフォルトでは無効になっているものもありますし、tmux も制限付きで有効になっていました。tmux のドキュメント にはその理由も書いてあり、要するにセキュリティ的に好ましくないということです。
コマンドの出力内容によって、ユーザーの目には見えないところでこっそりクリップボードの内容が上書きされてしまう、というのは確かにあまりうれしい状態とは言えませんが、読み出されるわけではないのでそれ自体が致命的に危ないわけではありません。ただし意図せず書き換えられたものをうっかりペーストしたときに、それがそのままシェル上で実行されてしまうなら問題です。コピーは寛容にする代わりにペーストを安全にすることを考えます。
今度はブラウザで何か改行を含むようなテキストをコピーして、シェルのプロンプトが表示された状態のターミナルにペーストしてみます(Ctrl-Shift-V
でペーストできることが多いと思います)。そうすると、改行が含まれていたとしてもコマンドが実行はされずに、ペーストした内容がそのまま表示されると思います。
一方で、tmux のコピーモードでコピーした内容を tmux からペースト(デフォルトでは C-b ]
)すると、どうなるでしょうか。実は tmux 3.2 以降であればターミナルからペーストしたのと同じ状態になります。これよりも古い場合、改行が含まれていると次々入力がコマンドとして実行されてしまいます。OSC 52 でコピーしたものはクリップボードだけではなく tmux のバッファにも保存されていますので、これはセキュリティ的に良くないです。
tmux.conf に以下のような設定を書いてペーストの動作を上書きしましょう(tmux 3.2 以降ではすでにこれが設定されています)。
bind ] paste-buffer -p
paste-buffer
に -p
オプションを付与することで、tmux からペーストした場合でも、ターミナルからペーストした場合と同様に改行を含むデータが自動的に実行されないようになります。
screen から移行してきたユーザーには、tmux.conf に以下のように書いている人も多いと思います。
bind C-] paste-buffer
paste-buffer
には必ず -p
をつけるものだと思ってください。
bind C-] paste-buffer -p
コピー用のコマンドを作る
今度は xclip や wl-copy のようなコマンドが欲しくなるかもしれません。tclip という名前の簡単なスクリプトを用意しましょう。
#!/bin/sh
if [ $# -eq 0 ]; then
printf "\e]52;c;%s\a" "$(base64 -w 0)"
else
printf "\e]52;c;%s\a" "$(printf "%s" "$*" | base64 -w 0)"
fi
以下のようにテキストを直接指定するか、
$ tclip this is tclip test
標準入力からデータを与えます。
$ tclip < /etc/group
OSC 52 を利用しているので、ローカルでもリモートでもどこから実行しても、ローカルのクリップボードにコピーできます。
mutt/neomutt などを利用している場合に urlview というプロブラムでリンクを抽出することがありますが、urlview の COMMAND として tclip を直接使用することもできます。
neovim からのコピーを便利に
neovim では Clipboard integration として tmux に対応しています。具体的には '+' というレジスタに書き込むと tmux のペーストバッファにコピーしてくれます。tmux にコピーすれば OSC 52 で手元のクリップボードまで透過してきますので、neovim でコピーしたものを一気に手元のクリップボードに持ってくることができます。もちろん、ネストした tmux の奥深くの neovim だったとしてもです。
個人的なおすすめは、以下のような設定を行うことです。
nnoremap + <Cmd>let @+ = @@<CR>
ノーマルモードで +
を入力すると、デフォルトで使用される無名レジスタの値を '+' レジスタにコピーします。3yy
で 3 行コピーしてから +
とすると、その 3 行が手元のブラウザの中にペーストできる状態になります。
neovim の内部的なコピーの仕組みを使うため、signcolumn が有効でも virtual text が表示されていてもそれらがコピーされて困ることはありません(neovim の中で tmux 的なコピーをしようとするとこれが鬱陶しいことがあります)。
なお、neovim では clipboard=unnamedplus
という設定で neovim 自体のクリップボードと tmux のクリップボードを同期して使うこともできるのですが、これはあまりおすすめしません。(使ってみればわかると思うのですが)単純に使いにくいのと、文字列をコピーするたびに同じデータ量の見えない文字列が(場合によっては ssh を通って)送られてくることになるので、効率も悪いです。
まとめ
- OSC 52 に対応したターミナルを使いましょう。
- tmux では OSC 52 を使えますが、ペーストの設定に気をつけましょう。
- tclip のようなコマンドを用意しておくと便利です。
- エディタも統合できるとさらに便利です。
それでは快適なターミナルライフを!
Discussion