📎

OSC 52 で出力をクリップボードにコピーするためのワンライナー

に公開

ワンライナーまとめ

./cmd の出力をクリップボードにコピーできるワンライナー

# サブシェル (`(...)`) or グループコマンド (`{...}`)
$ ./cmd | (echo -e "\e]52;;"; base64 | tr -d "\n"; echo -e "\e\\")
$ ./cmd | {echo -e "\e]52;;"; base64 | tr -d "\n"; echo -e "\e\\"}

# $(...) で直接埋めこむ
$ echo -ne "\e]52;;$(./cmd | base64 | tr -d '\n')\e\\"
# 端末によっては `tr -d '\n'` が不要 (e.g. Wezterm)
$ echo -ne "\e]52;;$(./cmd | base64)\e\\"

# xargs (置換文字列長制限に注意)
$ ./cmd | base64 | tr -d "\n" | xargs -I{} echo -n $'\x1b]52;;{}\x1b\\'
# For Mac (出力が長いとき)
$ ./cmd | base64 | xargs -I{} -S 1000000 echo -n $'\x1b]52;;{}\x1b\\'

グループコマンドのバージョンはほとんどそのままシェル関数として使うことができます。

osc52() {echo -e "\e]52;;"; base64 | tr -d "\n"; echo -e "\e\\"}
./cmd | osc52

OSC 52 とは?

OSC は Operating System Command の略です。
Operating System Command は何かというと、ESC ] ... ESC \ の並びのエスケープシーケンスのことです (ESC0x1b の 1 byte)。
ESC ] の直後に数字が続くパターンが多く、特に ESC ] 52 を先頭にしたエスケープシーケンスを OSC 52 といいます。

それで、この OSC 52 が何をしてくれるのかというと、テキストをクリップボードにコピーすることができます。
具体的な書式は以下のようになっています (空白は実際には入力しません)。

ESC ] 52;<Pc>;<Pd> ESC \

c.f. XTerm Control Sequences: Operating System Commands

<Pc> はクリップボードの種類を表わす文字列です。空だと s0 と同じ意味になるようです。詳しい説明は XTerm Control Sequences のページを参考にしてください。
<Pd> はクリップボードにコピーしたいデータを base64 エンコーディングした文字列です。

一つ例を試してみましょう。
foo という文字列をクリップボードにコピーしたいとします。まずは base64 エンコーディングしてみます。

$ echo -n foo | base64
Zm9v

base64 エンコードした結果 Zm9V という文字列になることがわかりました。
これを OSC 52 のエスケープシーケンスに埋めこんで実行します。

$ echo -n -e "\e]52;;Zm9v\e\\"

この状態で Ctrl + V などでペーストしてみると、foo という文字列が入っているのが確認できると思います。

OSC 52 は何がうれしいの?

コマンドの出力コピーするだけなら pbcopy とか clip.exe とか xsel -b とか使えばいいじゃんと思う方もいらっしゃるかもしれません。
確かにローカルの開発環境で使うだけなら以上のようなコマンドに標準出力を流せば十分です。

しかし、よく困るのが SSH でリモートに接続した際のコピーではないでしょうか?
X11 Forwarding を設定していれば xsel -b 等を用いてコピーしてくることはできるのですが、ローカルに X server を立てておかないといけないとか、リモート側に xsel コマンドを入れとかないといけないとか、そもそも X11 Forwarding が許可されていない場合があるとか、いろいろと面倒臭い部分も多いのです。

OSC 52 は端末が処理するので、SSH していようと何だろうと端末さえ OSC 52 に対応していれば簡単に使うことができるのが嬉しいポイントです。
唯一注意すべきなのは、端末が対応しているか、また対応しているとして有効化しているかという点になります。筆者が普段使っている Wezterm は特に設定しなくても OSC 52 が有効になっているようです。

tmux 用の設定

SSH 先では tmux を使う方が多いでしょう。
tmux は端末上で動いているアプリケーションの出力を一回 tmux 側で吸いとって、場合によってはいろいろと処理をしてから外側の実際の端末エミュレータに渡してくるので、ちょっと注意が必要です。
OSC 52 に関しては基本は以下を .tmux.conf に設定しておけば良いと思います。

set-option -s set-clipboard on

詳しくは以下などを参考にしてください。

https://zenn.dev/tennashi/scraps/04e226127a4b1d

https://github.com/tmux/tmux/wiki/Clipboard

ワンライナーでコピー

さて、本題のワンライナーを紹介します (bash, zsh 用です)。
(2024/12/23 xargs 以外の実装の追加, xargs を使う方法の注意点など大幅に改訂)

$(...) で埋めこむ

まず $(...) (または `...`) を用いてコマンドの結果を直接埋めこむのが比較的素直なやり方でしょう。

$ echo -ne "\e]52;;$(./cmd | base64 | tr -d '\n')\e\\"

base64 は出力に改行を含む [1] ので、tr -d '\n' で改行を削除しておいたほうが無難でしょう [2]。端末の実装依存だと思いますが、Wezterm では OSC 52 のデータの中にある改行は無視してくれる仕様になっているようで、以下でも動きます。

$ echo -ne "\e]52;;$(./cmd | base64)\e\\"

これならだいぶ短くて、単発で使うには使い勝手が良さそうですね!
シェルの実行履歴から使うときにコマンドが真ん中のほうにあるとちょっと編集しづらいのがやや難点かもしれません。

xargs を使って実現

echo への文字列の埋めこみは xargs で実現することもできます。
このようにすると、

# Mac 環境 では `tr -d "\n"` は基本的に不要
$ ./cmd | base64 | tr -d "\n" | xargs -I{} echo -n $'\x1b]52;;{}\x1b\\'

xargs を使うのは慣れていれば比較的思い付きやすいかと思いますが、いくらか罠があります。

ここでは xargs -I{} を使って {} のプレースホルダーに base64 データを埋めこんでいます。
xargs -I{} は入力の改行ごとに {} の置換を行ってコマンドを起動するため、tr -d "\n" による改行の削除は Linux (GNU Coreutils 版 base64) ではほぼ必須です (脚注 1. 参照)。
一方、MacOS の base64 は末尾にしか改行を置かないため tr -d "\n" は無くても動きます。

また xargs を使う際にちょっと注意が必要なのが、echoxargs から起動されるのでシェル組み込みではなく外部コマンドの /bin/echo 等が呼ばれるという点です。すると、\e -> ESC 等を解釈してくれる-e オプションが使えなかったりすることがあります [3]
そこで、$'...' という bash/zsh の特殊なクオートを使っています [4]。この $'...' の中では \xHH の形式の 16進記法を対応する 1 byte に展開してくれるので今回のテクニックが成立します。この展開は bash/zsh によるものなので、xargs および echo コマンドに引数を渡すよりも前に行なわれているのがポイントです。

出力文字列長の制限

注意点として、xargs -I を使う場合にはプレースホルダーの置換文字列の長さに制限があります。文字列長の制限については調べていたら長くなってしまったので、別記事にまとめました。詳しくはこちらを参照してください。

https://zenn.dev/sankantsu/articles/ca15e7b0e18387

Mac の場合はデフォルトでプレースホルダーに代入される行の長さが 255 以上になると置換を行ってくれません。この制約を回避するには -S オプションで置換できる文字列長の上限を変更します。適当に -S 1000000 など大きめの数値を指定しておけば良いでしょう。

$ ./cmd | base64 | xargs -I{} -S 1000000 echo -n $'\x1b]52;;{}\x1b\\'

Linux の xargs (GNU findutils 版) では Mac よりはだいぶ制約が緩いですが、それでも ./cmd の出力が長くなりすぎる (10万文字程度) と xargs: argument list too long のエラーになります。
この場合は次のサブシェルを使ったバージョンや、最後に紹介するシェル関数・シェルスクリプトを使ってください。

サブシェル/グループコマンドを使って実現

文字列の echo の途中に標準入力を加工したデータを割りこませるには、サブシェルやグループコマンドを使ってコマンドをくっつけることによって実現することもできます。

$ ./cmd | (echo -e "\e]52;;"; base64 | tr -d "\n"; echo -e "\e\\")
# or
$ ./cmd | {echo -e "\e]52;;"; base64 | tr -d "\n"; echo -e "\e\\"}

これらの方法では ./cmd の出力は (...){...} の複合コマンド全体に渡されます。最初のコマンド echo -e "\e52;;" は標準入力を消費せず、そのまま 2 番目の base64 | tr -d "\n" に渡されます。ここでコピーしたいデータの base64 出力された後、最後の echo -e "\e\\ でエスケープシーケンスを閉じます。
この方法は基本的に xargs のときのような出力長の制限はありませんし、そのままシェル関数定義にしやすいのもメリットです。

osc52() {echo -e "\e]52;;"; base64 | tr -d "\n"; echo -e "\e\\"}
./cmd | osc52

便利なシェル関数・シェルスクリプトもあるよ

正直なところ、何回も使うのであればたぶん以下のようなシェル関数やシェルスクリプトを使ったほうが良いです。

https://gist.github.com/ttdoda/6c30de9a5f2112e72486

https://github.com/libapps/libapps-mirror/blob/main/hterm/etc/osc52.sh

関数定義するのもめんどいし、シェルスクリプト持ってくんのもめんどくさいし、とにかく今 1 回だけコピーしてきたいんじゃ!ってときにサッとワンライナーで書けたら役に立つ...かもしれません。
以上

脚注
  1. Linux (GNU Coreutils 版) の base64 はデフォルトで 76 byte ごとに改行する (manual)。MacOS 版の base64 は全体の出力の末尾に改行を含む。 ↩︎

  2. RFC4648: 3.3 Interpretation of Non-Alphabet Characters in Encoded Data には "Implementations MUST reject the encoded data if it contains characters outside the base alphabet when interpreting base-encoded data, unless the specification referring to this document explicitly states otherwise." とあります。そして、OSC 52 の実質的な仕様とみなせるであろう XTerm Control Sequences には特に base64 データ中の改行を無視するようにすべきという言及は見当たりません。したがって、(むしろ RFC4648 に忠実であれば) 端末によっては改行を含む base64 データをはじいてしまうかもしれません。
    そのため、tr -d '\n' を入れておいたほうがおそらく移植性は高いように思われます。とはいえ、ワンライナーなんて基本その場で動けばよいものなので、実際に自分の使っている端末で確認して tr -d '\n' 無しでも動いていそうなら外してしまっても良いでしょう ↩︎

  3. 筆者の使っている Mac 環境だと使えませんでした。Linux 環境なら GNU Coreutils 版が入っていることが多いと思うのでたぶん -e オプションも使えることが多いと思います ↩︎

  4. man bash(1): QUOTING ↩︎

GitHubで編集を提案

Discussion