コマンド入力一撃で端末を大量に分割してタスクを瞬殺するtmux-xpanes

# この記事の要点

コマンド一撃でtmuxのウィンドウを大量に分割してコマンドの同時実行を助ける`xpanes`（イクスペインズ）というコマンドを作りました。一斉に複数のホストに`ping`を送ったり、`ssh`でログインして同時操作したり、一斉に複数のファイルを`tail -f`したりするのに使えます。
操作ログの保存機能、標準入力を受け取ってコマンドを作成する機能などなど、機能も充実していて便利です。tmuxを知らない方でも単純な例だけであれば覚えることは少ないので、ぜひお試しを。
下記のアニメを見ていただければ、どんな感じのものを作成したのかお分かりいただけると思います。

![tmux-xpanes Animation](https://raw.githubusercontent.com/wiki/greymd/tmux-xpanes/img/movie.gif)

[![logo](https://raw.githubusercontent.com/wiki/greymd/tmux-xpanes/img/xpanes_logo_1.png)](https://github.com/greymd/tmux-xpanes)

**Github:** https://github.com/greymd/tmux-xpanes

# 背景

[tmux](https://tmux.github.io/)を使っている方は、どのような用途でtmuxを役立てることが多いでしょうか？開発の効率を上げるため[^tmux4dev]、あるいは複数のウィンドウ[^tmux_window]やペイン[^pane]を駆使してサーバのモニタリングをするためなど、人により様々だと思います。

その中でもよく見かけるのが、tmuxでウィンドウを分割し、`ssh`で複数のホストに接続する方です[^sshscripts1]。かなり多くの方が、tmuxのサブコマンドを並べてスクリプトを組んでいるようです[^sshscripts2] [^sshscripts3] [^sshscripts4] [^sshscripts5]（そしてなかなか思い通りに分割できずに苦しんでいる方も多かったのではと思います）。

[^tmux_window]: この記事では「ウィンドウ」と「ペイン」という言葉を使います。両方とも、tmuxで使われる用語として使いますので、GUIのウィンドウとは異なるものです。定義は[こちら](http://qiita.com/b4b4r07/items/01359e8a3066d1c37edc#%E3%83%9A%E3%82%A4%E3%83%B3%E3%82%A6%E3%82%A3%E3%83%B3%E3%83%89%E3%82%A6%E6%93%8D%E4%BD%9C)によくまとまっていました。

[^pane]: ペインとは「ウィンドウを分割した領域」のことです。[参考](http://qiita.com/b4b4r07/items/01359e8a3066d1c37edc#%E3%83%9A%E3%82%A4%E3%83%B3%E3%82%A6%E3%82%A3%E3%83%B3%E3%83%89%E3%82%A6%E6%93%8D%E4%BD%9C)

[^tmux4dev]: 私は開発中に、エディタを開きつつ端末からコマンドを走らせるためにtmuxを活用しています。
[^sshscripts1]: 例えばこのような方です [tmuxで大量のサーバーを操る最高の方法 - Qiita](http://qiita.com/catatsuy/items/3aa7bd49399bba66d8f1)
[^sshscripts2]: [tmux-csshで複数サーバに対するカジュアルなオペレーション](http://shase428.hatenablog.jp/entry/2016/08/05/141627)
[^sshscripts3]: [tmuxで複数にsshしてコマンドシンクするスクリプト by @mikeda](https://gist.github.com/yosugi/089899d5099664d56846b143c750ca2e)
[^sshscripts4]: [Start multiple synchronized SSH connections with Tmux](https://gist.github.com/dmytro/3984680)
[^sshscripts5]: [tmuxで複数サーバの同時オペレーション](http://tech.naviplus.co.jp/2014/01/09/tmux%E3%81%A7%E8%A4%87%E6%95%B0%E3%82%B5%E3%83%BC%E3%83%90%E3%81%AE%E5%90%8C%E6%99%82%E3%82%AA%E3%83%9A%E3%83%AC%E3%83%BC%E3%82%B7%E3%83%A7%E3%83%B3/)

しかし、`ssh` だけではなく、他のコマンドもウィンドウ分割をして実行することができれば、複数のホストに`ping`を送ったり、`curl`を定期的に叩いて簡単な死活監視をしたりすることができるので色々便利になるのではないかと思います。「任意のコマンドを、ウィンドウを分割しつつ複数のペイン上で実行する」という操作を、もっと部品化できないかと考えたのが [tmux-xpanes](https://github.com/greymd/tmux-xpanes) というソフトウェアを作成した経緯です[^namedefinition]。

[^namedefinition]: tmux-xpanesソフトウェア名の総称で、`xpanes`はコマンド名として記事内では使います。

# tmux-xpanesとは?
今回作成したtmux-xpanesというソフトウェア、すなわち`xpanes`コマンドは、UNIXの`xargs`コマンドを叩くような気軽さで、ウィンドウをペイン[^pane]に分割できるコマンドです。そのため名前はUNIXのコマンドである`xargs`とtmuxのペイン(pane)をかけ合わせたものになっています。

# 似たようなソフトウェア

### [tmuxinator](https://github.com/tmuxinator/tmuxinator)
tmuxのセッション情報を管理するソフトウェアです。ペインの配置と分割方法をYAMLファイルに記述すると、そのファイルを読むだけで、特定のtmuxセッションの作成が可能となります。日常的に繰り返し同じようなtmuxの分割スタイルを取る方には大変重宝するソフトなんじゃないかなと思います[^tmuxinator_ex]。
しかし、その場限りの思いつきでウィンドウを分割し、設定ファイルもソースコードも残さずにタスクを瞬殺する、シェル芸[^shell-gei]のような思想では使いにくいですし、そういうコンセプトのソフトウェアでは無いです。

[^shell-gei]: マウスも使わず、ソースコードも残さず、GUIツールを立ち上げる間もなく、あらゆる調査・計算・テキスト処理をCLI端末へのコマンド入力一撃で終わらすこと。あるいはそのときのコマンド入力のこと。 from [シェル芸の定義バージョン1.1](シェル芸の定義バージョン1.1)

[^tmuxinator_ex]: 例えば、開発時のエディタとターミナルの分割方法が常に特定のパターンでないと落ち着かない人とかです。


### [tmux-cssh](https://github.com/dennishafemann/tmux-cssh)
挙動としてはtmux-xpanesと比較的似たソフトウェアですが、sshでのログインのみに特化しています。コマンドの引数をホスト名として扱い、ウィンドウを複数のペインに分割して`ssh`で接続をします。ホストの集合をファイルで管理してそれを読み込んでログインもできます。ある程度数が多い、特定の組み合わせのホストに頻繁にログインする方には良いかもしれません。

しかしながら、個人的にはまだ不便を感じることが多かったです(これも今回作成したモチベの一つです)。理由としては、コマンドの柔軟さに欠ける点[^cssh_func]、個人的に欲しい機能が無い点[^cssh_func2]、既に動作中のtmuxセッションからは実行できない点などが挙げられます[^cssh_issue]。

[^cssh_func]: `ssh`のみに特化しているが故に、柔軟な操作ができないことです。例えば、特定のホストには`-i`オプションで特定の秘密鍵を使いたい、初回ログイン時のyes/noの質問を避けるために`StrictHostKeyChecking=no`してログインしたい、などということができません。

[^cssh_issue]: 一応Issueとしては上がっているようで、PRもあるようなのですが、メンテされてないようで、取り込まれてないです。。 [Feature request: Use tmux-cssh from within tmux (gives nesting error now) · Issue #30 · dennishafemann/tmux-cssh](https://github.com/dennishafemann/tmux-cssh/issues/30) `xpanes`は、動作モードを別にすることで、既存のtmuxセッション内からの実行が可能です。

[^cssh_func2]: `xpanes`は、汎用的なコマンドの実行をサポートしているのに加え、後述するロギング機能や標準入力を受け取って処理する機能もあります。`xpanes`はその場限りの使い捨てのコマンドを実行しやすくするというコンセプトを持たせており、ちょっと`tmux-cssh`とは違うコンセプトのソフトウェアですね。ちなみに`xpanes`には`--ssh`オプションと、`~/ssh/config`を自動的に取得して補完に利用するZshの補完関数が存在します。そのため私が個人的に使う限りは接続するホスト名の列挙にはあまり困ったことはないです。`ssh`でログインするホストは、名前に専用のprefixをつける、複数の名前を持たせるなど工夫して全て`~/ssh/config`に記述してしまえば良いと私は考えています。

# `xpanes`の動作環境

下記の2つのソフトウェアを動作要件として定め、動作を確認しています。
tmuxさえ入れば、あとは多くのUNIX環境で動くのではないかと思います[^oldenv]。

* Bash 3.2 以上
* tmux 1.6 以上

[^oldenv]: 正直なところかなり古い環境を動作要件として設定しています。本当はtmux 1.8以上とbash 4.0以上くらいにしたかったんですが、私が個人的に使いたい環境が古かったので、こうしてます。

# インストール方法

詳しくは[Githubのwiki > Installation](https://github.com/greymd/tmux-xpanes/wiki/Installation)をご覧ください。いくつかインストール方法をご紹介します。

## apt
Ubuntu[^ubuntu_support]向けの方法です。

[^ubuntu_support]: インストールが可能なUbuntuのバージョンは[tmux-xpanesのWiki](https://github.com/greymd/tmux-xpanes/wiki/deb-package#supported-ubuntu-versions)を参照してください。Preciseは最近サポートが切れたので用意していません。マニュアルでインストールしてください。

```bash:Terminal
# `add-apt-repository` がない場合はインストール
$ sudo apt install software-properties-common

# PPAリポジトリを追加
$ sudo add-apt-repository ppa:greymd/tmux-xpanes

# インストール
$ sudo apt update
$ sudo apt install tmux-xpanes
```

## Homebrew

macOSをお使いの方は[Homebrew](https://brew.sh/index_ja.html)が使えます。

```bash:Terminal
$ brew install tmux-xpanes
```

## 手動インストール

(この方法をとる方はtmuxを自前で入れる必要があります。)

```bash:Terminal
# wgetで落とす
$ wget https://raw.githubusercontent.com/greymd/tmux-xpanes/master/bin/xpanes -O ./xpanes

# パスを通す
$ sudo install -m 0755 ./xpanes /usr/local/bin/xpanes
```

## Zsh ユーザ向け

(この方法をとる方はtmuxを自前で入れる必要があります。)

Zshのプラグインマネージャに対応しています。下記は[zplug](https://zplug.sh)を使った例です。Zshの補完関数も有効になります。

```bash:terminal
zplug "greymd/tmux-xpanes"
```

### Zsh 補完関数のインストール

Zsh用の補完関数は便利なのでZshをご利用の方はぜひご活用ください。実行可能なオプションや引数の列挙に加え、後述する`--ssh`オプション後に、`~/.ssh/config`からホスト名を補完してくれる機能などが揃っています。

プラグインマネージャをご利用の方は、既に述べた方法で補完関数が有効になっています。そうではない方は、[Wikiに記載の方法](https://github.com/greymd/tmux-xpanes/wiki/Installation#installing-zsh-completion)に従ってください。


# `xpanes` コマンド
基本的なインストール方法をとると`xpanes`と`tmux-xpanes`コマンドの2種類がインストールされますが、どちらも同じコマンドになります[^whyprefix]。お好きな方をご利用ください。記事内では`xpanes`コマンドを使います。またバージョンは 2.0.2 になります[^bugfix]。

[^bugfix]: 記事公開当初は2.0.1でしたがアップデートしました。特定のtmuxの設定が有効だとうまく動かなかったり、fishをログインシェルにすると動かなかったりしてたのをなおしました。試してみたがうまく動かなかったという方は更新を。[Release Release: v2.0.2 · greymd/tmux-xpanes · GitHub](https://github.com/greymd/tmux-xpanes/releases/tag/v2.0.2)

```
$ xpanes -V
xpanes 2.0.2
```

[^whyprefix]: [tmux-cssh](https://github.com/dennishafemann/tmux-cssh) のようにtmux関連のコマンドには`tmux-`というprefixをつけておいたほうが`tmux-`と打ってtabキーを押すだけでコマンド名が候補として現れるので、「tmux関連のコマンドなんかあったよな」と漠然と思っている人にも使ってもらえるかな、という配慮です。

## 簡単な動作例

インストールが完了したら、まずお試しに下記のコマンドを実行してみましょう。

```bash:Terminal
$ xpanes 1 2 3 4
```

すると、このような出力が得られるはずです。

```bash:Terminal
$ echo 1                       │$ echo 2
1                              │2
                               │
                               │
                               │
                               │
                               │
                               │
-------------------------------+-------------------------------
$ echo 3                       │$ echo 4
3                              │4
                               │
                               │
                               │
                               │
                               │
                               │
```

デフォルトの動作では与えられた引数を`echo`コマンドの引数として実行します。実行時には新規にウィンドウが作成され、与えられた引数分ウィンドウが分割されます。

tmuxを使い慣れていない方は、キーバインドがわからないと思います。ここから何をしてよいかわからないかもしれませんが、心配はいりません。`exit` と打った後にEnterキーを打つと、全てのペインが閉じ、元のスクリーンに戻ります[^howtoclose]。

[^howtoclose]: 開いているのはtmuxのペインなので、もちろんtmuxのキーバインドを使って閉じても良いです。

```bash:Terminal
$ exit                         │$ exit
                               │
                               │
                               │
                               │
                               │
                               │
                               │
-------------------------------+-------------------------------
$ exit                         │$ exit
                               │
                               │
                               │
                               │
                               │
                               │
                               │
```

このように、デフォルトの状態では、キーを入力すると全てのペインにその入力が反映されます（キー入力の同期を無効にする方法は後述）。

### `-c` オプションと `-I` オプション

`-c` オプションを使うことで、`echo`以外の任意のコマンドを実行することができます。例えば、下記のコマンドを実行してみましょう。

```bash:Terminal
$ xpanes -c 'seq {}' 1 2 3 4
```

すると、このような出力が得られます。

```bash:Terminal
$ seq 1                        │$ seq 2
1                              │1
                               │2
                               │
                               │
                               │
                               │
                               │
-------------------------------+-------------------------------
$ seq 3                        │$ seq 4
1                              │1
2                              │2
3                              │3
                               │4
                               │
                               │
                               │
```

`seq`コマンドは、連続した番号を出力するコマンドです。それが`-c` オプションにより指定されました。ご覧の通り `{}` という箇所がそれぞれの引数に置換されています。この`{}`という置換される文字列は `-I` オプションで変更することができます。このような感じです。

```bash:Terminal
$ xpanes -I@ -c 'seq @' 1 2 3 4
```

デフォルトの動作、すなわち`-c`オプションが指定されないと`echo {}`がデフォルトのコマンドとなるわけです。

### ブレース展開を活用しよう！

BashやZshなどにより提供される[ブレース展開](https://www.gnu.org/software/bash/manual/html_node/Brace-Expansion.html)を活用すると、連番や連続するアルファベットを生成するのに便利です。`xpanes` コマンドへの引数に活用してみましょう。

```bash:Terminal
# これは $ xpanes 1 2 3 4 と実行したのと同じです
$ xpanes {1..4}
```

いかがでしょう？ここまでの説明だけでもかなり使えそうな気がしてきたんじゃないでしょうか。

## 動作仕様

基本的な動作は知っていただいたと思います。これから `xpanes` コマンドを具体的に役立てるための応用例を見せていきたいと思うのですが、その前に、このコマンドの動作モードについて説明します。`xpanes` コマンドは、状況によって3種類の異なる動作をするコマンドです。

### tmuxセッション外の動作

tmuxを普段使っていない方は、最初にこのモードで使うことになると思います。`xpanes` コマンドをインストールし、端末を開いてそのままこのコマンドを実行したとき`xpanes`コマンドは**新規にtmuxのセッション[^tmux_session]とその中にウィンドウを作成**します。そのウィンドウをさらにペインに分割して、端末に表示(アタッチ[^tmux_attach])します。

[^tmux_session]: tmuxの用語にsession(セッション)という言葉があります。「ウィンドウの集まりを総称したもの」です。[参考](http://qiita.com/b4b4r07/items/01359e8a3066d1c37edc#%E3%83%9A%E3%82%A4%E3%83%B3%E3%82%A6%E3%82%A3%E3%83%B3%E3%83%89%E3%82%A6%E6%93%8D%E4%BD%9C)

[^tmux_attach]: tmuxの用語にattach(アタッチ)という言葉があります。これは「作成したセッションを、実際にユーザが見える形で画面に表示する」くらいの意味で捉えていただいて構いません。ちなみにtmuxのセッションはアタッチされていなくても、裏で起動させることができます。その状態にすることをデタッチとも呼びます。

### tmuxセッション内の動作

常にtmuxを使う方（例えば端末を開いた瞬間にtmuxが最初から開き、基本的にその中で普段の作業をするような方）もいると思います。そのような方の場合、すでに存在するtmuxのセッション内から`xpanes`コマンドを実行することになると思います。この時`xpanes`コマンドは**既存のtmuxセッションに更に新規ウィンドウを作成**し、そのウィンドウを分割して端末にアタッチします。

### tmuxセッション内かつ & 標準入力があるとき (Pipeモード)

上記の「tmuxセッション内の動作」に加えて、コマンドが標準入力を受け付ける状態の時。具体的には他のコマンドとパイプ ` | ` が `xpanes` コマンドに対して繋がったときは、Pipeモードという動作モードになります。これはUNIXの `xargs` コマンドと似た動作をするモードです。詳しくは後述します。

## 応用例

ここからは、私が`xpanes`コマンドを使ってよくやる応用例を紹介したいと思います。

### 複数ホストへの同時ping

入力

```bash:Terminal
$ xpanes -c "ping {}" 192.168.1.{5..8}
```

出力

```bash:Terminal
$ ping 192.168.1.5             │$ ping 192.168.1.6
                               │
                               │
                               │
                               │
                               │
                               │
                               │
-------------------------------+-------------------------------
$ ping 192.168.1.7             │$ ping 192.168.1.8
                               │
                               │
                               │
                               │
                               │
                               │
                               │
```

既に述べたブレース展開を使った例です。IPアドレスや連番が含まれているホスト名を列挙するのに大変便利で、`xpanes`コマンドと相性抜群です。このように結果が一覧して見えるので、死活監視にとても良いです。

### 複数同時tail

入力(本来1行でOKですが、エスケープ文字` \ `で適宜改行しています)

```bash:Terminal
$ xpanes -c "tail -f {}" \
/var/log/apache/{error,access}.log \
/var/log/application/{error,access}.log
```

出力

```bash:Terminal
$ tail -f /var/log/apache/error.log       │$ tail -f /var/log/apache/access.log
                                          │
                                          │
                                          │
                                          │
                                          │
                                          │
                                          │
                                          │
                                          │
------------------------------------------+------------------------------------------
$ tail -f /var/log/application/error.log  │$ tail -f /var/log/application/access.log
                                          │
                                          │
                                          │
                                          │
                                          │
                                          │
                                          │
                                          │
                                          │
```

ログイン先のホストにtmuxが入っていれば、こんな感じに一気に複数のアクセスログを見るのに使えるかもしれませんね。アプリケーションサーバにtmuxが入っていない？ホストが一種類であれば、こんな感じに`ssh`経由で実行すれば良いだけですね。

```bash:Terminal
# 'ssh user@host' が追加された。
$ xpanes -c "ssh user@host tail -f {}" \
/var/log/apache/{error,access}.log \
/var/log/application/{error,access}.log
```

### 複数ホストへの同時ssh

入力

```bash:Terminal
$ xpanes -c "ssh myuser@{}" host1 host2
```

出力

```
$ ssh myuser@host1             │ $ ssh myuser@host2
                               │
                               │
                               │
                               │
                               │
                               │
```

この例ができると嬉しい方が一番多いんじゃないかと思います。この例では、ログインするホスト全てに`myuser`というユーザを使っています。ホストごとにユーザを変えたい場合は、こんな感じにすればよいだけですね。

```bash:Terminal
$ xpanes -c "ssh {}" myuser1@host1 myuser2@host2
```

#### 専用オプション

実は、`--ssh` というssh専用のオプションを用意しています。

```bash:Terminal
$ xpanes --ssh myuser1@host1 myuser2@host2
```

これは、下記のコマンドを実行した場合と同じです[^sshoption]。

[^sshoption]: これも完全に自分の好みで作りました。

```bash:Terminal
$ xpanes -c "ssh -o StrictHostKeyChecking=no {}" myuser1@host1 myuser2@host2
```
`ssh`コマンドに`-o StrictHostKeyChecking=no`というOpenSSHの警告文を無視するオプションをつけて実行します。`ssh`でどこかのホストに初めてログインすると、OpenSSHが発する警告文にyes/noで答えなければいけません。それが面倒な方はこのオプションを使うのも良いでしょう。[^ssh_alert]

[^ssh_alert]: 普段から大量のホストにログインする方は、何らかの理由でフィンガープリントが変わって警告文が出現することがよくあると思います。そのような時に**あくまで接続先が明確に信頼できる確証が持てる場合に**一々回答するのが面倒な方はこれを使うと良いと思います。

#### 複数ペインへの同時入力を無効にする

キーボードの入力は複数のペイン間で同期しないほうが良い、という状況もあると思います。そんな時は`-d` (あるいは`--desync`) オプションを使うと、キーボードを入力しても操作が一つのペインにしか適用されません[^howsync]。再度同期したい方は、tmuxの`synchronize-pane`オプションを`on`にして下さい。

```bash:Terminal
$ xpanes -d -c "ssh myuser@{}" host1 host2
```

[^howsync]: これはtmuxの`synchronize-pane`オプションを`off`にしています。デフォルトでは`on`にします。

#### 操作ログの保存

`--log` オプションという、操作ログを保存する機能があります。これは、分割されたペインの出力や、ペインに対するキーボードからの入力を全てペインごとに分割されたファイルに書き出してくれる機能です。

```bash:Terminal
$ xpanes --log=~/operation_log -c "ssh myuser@{}" host1 host2
```

上記の例では`--log`というオプションに続いてログを保存するディレクトリを指定しています。その後、操作をするとログが保存されます。引数として`host1`と`host2`を渡したのでそれぞれをファイル名に持つファイルができます。

```bash:Terminal
$ ls ~/operation_log/
host1-1.log.2017-03-15_21-30-07
host2-1.log.2017-03-15_21-30-07
```

ログのファイル名のフォーマットは`--log-format`オプションで変更することができるので`xpanes --help`で使い方を参照してみて下さい。

なお、このロギング機能に関しては、tmuxのバグで利用できないtmuxのバージョンが存在するのでご注意下さい。[Githubのwiki > Known Bugs](https://github.com/greymd/tmux-xpanes/wiki/Known-Bugs#logging-feature) を参照して下さい。

### 一発で複数のモニタリング用コマンドを実行

`-e` オプションを使うと、与えられた引数をそのままコマンドとして実行します。例えば下記の例を実行してみましょう。

```bash:Terminal
$ xpanes -e "top" "vmstat 1" "watch -n 1 df"
```

するとこのような出力が得られます。`top`でロードとプロセスを確認しつつ、`vmstat 1`で毎秒CPUとメモリの利用状況を確認しつつ`watch -n 1 df` で毎秒ストレージの残量を確認します。

```
$ top                          │$ vmstat 1
                               │
                               │
                               │
                               │
                               │
                               │
-------------------------------┴------------------------------
$ watch -n 1 df






```

この例は、下記のコマンドを実行した場合と同等です。

```bash:Terminal
$ xpanes -c "{}" "top" "vmstat 1" "watch -n 1 df"
```

#### 分割方法の変更

`-l` オプションに続き、特定の引数を渡すと、ペインの分割方法を変更することができます。下記の例はペインを上から下に垂直方向に均等に並べる`ev`(tmuxで`even-vertical`と呼ばれている分割方法)という命令を適用する例です。

```bash:Terminal
$ xpanes -l ev -c "{}" "top" "vmstat 1" "watch -n 1 df"
```

下記のようにペインが垂直方向均等に並びました[^layout_naming]。

[^layout_naming]: 余談ですが、ペインを分割する線は、水平に伸びているので、最初この状態が水平(horizontal)かと勘違いしていました。ややこしいと思うのは私だけでしょうか？

```bash:Terminal
top




-------------------------------------------------------------
vmstat 1




-------------------------------------------------------------
watch -n 1 df




```

同様に、水平方向に均等に並べる`eh` (tmuxでは`even-horizontal`と呼ばれる)や最初のウィンドウを大きめにして、残りは垂直あるいは水平に均等に並べる`mv`(`main-vertical`)、`mh`(`main-horizontal`)もあります。覚えられない場合は`xpanes --help`で使い方を参照して下さい。

### 複数ユーザでの画面共有

`xpanes`コマンドを使うと`~/.cache/xpanes/socket` というファイルが自動的に作成されます。これはtmuxのソケットファイルと呼ばれ、セッションの情報を持っています。このファイルを利用することで、複数のユーザ間でtmuxを使って画面(セッション)共有が可能です。

このソケットファイルはtmuxと同様に`-S`オプションを使うことで、任意のパスに作成することができます。

#### user1がxpanesとソケットを作成

```bash:Terminal(user1)
[user1@host] $ xpanes -S /home/user1/mysocket a b c d ...
```

#### user2がtmuxでソケットファイルを読み込む

```bash:Terminal(user2)
[user2@host] $ tmux -S /home/user1/mysocket attach
```

上記のようにすることで、user1とuser2の両者でリアルタイムな画面共有ができます。

## 複数ウィンドウを作成しつつペイン分割

`xpanes`の「tmuxセッション内で実行されると新規ウィンドウが作成される」という性質を利用して、複数ウィンドウの作成を比較的手軽にすることができます。`xpanes`を使って`xpanes`自身を呼び出す、ちょっとトリッキーな方法です。

下記のコマンドを実行してみましょう。

```bash:Terminal
$ xpanes -c "xpanes -I@ -c 'ssh @' {} && exit" \
  "groupA-host1 groupA-host2" \
  "groupB-host1 groupB-host2 groupB-host3" \
  "groupC-host1 groupC-host2"
```

すると、下記のような組み合わせで、新規ウィンドウが3つ作成されます。

|   | ペイン1              | ペイン2              | ペイン3              |
| ------  | -----              | -----              | -----              |
| ウィンドウ1 | `ssh groupA-host1` | `ssh groupA-host2` | ※なし               |
| ウィンドウ2 | `ssh groupB-host1` | `ssh groupB-host2` | `ssh groupB-host3` |
| ウィンドウ3 | `ssh groupC-host1` | `ssh groupC-host2` | ※なし               |

<!-- TODO: screen shot -->


なぜこのような動作になるのか、順を追って説明します。

```bash:Terminal
$ xpanes -c "xpanes -I@ -c 'ssh @' {} && exit" \
  "groupA-host1 groupA-host2" \
  "groupB-host1 groupB-host2 groupB-host3" \
  "groupC-host1 groupC-host2"
```

このコマンドを実行すると、まず最初に下記の結果が得られます。

```
$ xpanes -I@ 'ssh @' groupA-host1 groupA-host2 && exit │$ xpanes -I@ 'ssh @' groupB-host1 groupB-host2 groupB-host3 && exit
                                                       │
                                                       │
                                                       │
                                                       │
                                                       │
                                                       │
-------------------------------------------------------+-------------------------------------------------------
$ xpanes -I@ 'ssh @' groupC-host1 groupC-host2 && exit






```

まず、一番左上のペインに注目してみましょう。

```bash:Terminal
$ xpanes -I@ 'ssh @' groupA-host1 groupA-host2 && exit
```

このコマンドがtmuxセッション内で実行されると、新規にウィンドウを作成し、下記のようなコマンドを実行します。

```
$ ssh groupA-host1             │ $ ssh groupA-host2
                               │
                               │
                               │
                               │
                               │
                               │
```

`xpanes`コマンドと併記して`&& exit`というコマンドが指定されています。これにより**新規にウィンドウを作成した後に`xpanes`コマンドが実行されたペイン自身は閉じる**という挙動をするようになります。すなわち、新規に2分割されたウィンドウを作成した後、左上のペインは自動的に閉じることになります。

この挙動が同様に、右上、そして下のペインでも実行されます。`xpanes`コマンドを3つ実行した先ほどのウィンドウは、3つの新規ウィンドウを作成したのちに、保有するペインをなくして自滅します。結果的に、任意の個数のウィンドウを開きつつ、任意のペイン分割でコマンドを実行させることができます。

日常的に決まったウィンドウ分割方法をするのであれば、ウィンドウの名称やペインサイズも細かく設定できる[tmuxinator](https://github.com/tmuxinator/tmuxinator)を使ったほうが良いと思いますが、一回きりの使い捨てであれば、上記のようなコマンドで十分でしょう。先述した動作モードの動きを理解して慣れてしまえば、スムーズに上記のようなコマンドが記述できます。

## Pipeモード
パイプで標準入力を受け取っている場合、`xpanes`コマンドはPipeモードという動作モードになります[^whypipemode]。これはtmuxセッション内のみの動作になります[^pipe_without_tmux]。UNIXのコマンドである`xargs`を強く意識したコマンドです。

[^whypipemode]: 一つのコマンドが、標準入力が存在するときとしないときで大きく動作を変えるのは、UNIX哲学の観点からはあまり良い設計思想ではないかもしれません。しかし私はこのコマンドをどうしても`xargs`っぽくも使いたかったのです。シェル芸が捗るからです。叱らないでください。

[^pipe_without_tmux]: tmuxセッション外でも一応Pipeモードは起動できますが、手動のtmuxセッションのアタッチが警告文により促されます。tmux仕様なのか、UNIXの仕様なのかわからないのですが、パイプでつながってforkされた子プロセスからでは、親プロセスのttyが見えず、端末の表示ができないようです。

```bash:tmux_session
# Pipe モード
$ seq 3 | xpanes
```

上記のコマンドを実行すると、この様な出力が得られます。

```
$ echo 1                                  │$ echo 2
1                                         │2
                                          │
                                          │
                                          │
                                          │
                                          │
                                          │
                                          │
                                          │
------------------------------------------+------------------------------------------
$ echo 3
3                                          
                                          
                                          
                                          
                                          
                                          
                                          
                                          
                                          
```

Pipeモードは、下記の2つの点で、通常のモードとは異なる挙動をします。

1. **標準入力から受け取った1行が、通常モードでいう1引数のような扱いをされる**ようになる。
1. コマンドに対する **引数が実行されるコマンドの一部になる** (通常のモードの`-c`オプションに相当、デフォルトは`echo {}`)。

違う例をお見せしましょう。

```bash:tmux_session
# こんな挙動をするコマンド
$ seq 4
1
2
3
4

# これをxpanesに標準入力で与える
$ seq 4 | xpanes printf
```

結果はこうなります。

```
$ printf 2                                │$ printf 4
                                          │
                                          │
                                          │
                                          │
                                          │
                                          │
                                          │
                                          │
                                          │
------------------------------------------+------------------------------------------
$ printf 6                                │$ printf 8
                                          │
                                          │
                                          │
                                          │
                                          │
                                          │
                                          │
                                          │
                                          │
```

もちろん、`-c`オプションも`-I`オプションも使えます。

```bash:tmux_session
$ seq 4 | xpanes -c 'printf {}'
##  xpanes printf と xpanes -c 'printf {}' は同じ
```

ただしその場合、引数も両方同時に与えると、コマンドがどちらを使ってよいのかわからないので、エラーになってしまいます。

```bash:tmux_session
$ echo test | xpanes -c 'echo {}' echo
# Error: Both arguments and '-c' option are given.
# => エラーになる
```

`xpanes` コマンドで実行するコマンドにパイプを含めたいときは`-c`オプションを使ったほうが良いと思います。

```bash:Terminal
# => うまくいかない
$ echo hello | xpanes echo | grep hello
```

```bash:Terminal
# => うまくいく
$ echo hello | xpanes -c 'echo {} | grep hello'
```

### ホスト一覧表から一斉にsshする

Pipeモードの利点は、何かのファイルをパースした結果を、特別なことをせずに、そのまま`xpanes`の標準入力に与えることができる点です。たとえば、こんな感じの`~/.ssh/config`ファイルが存在したとします。

```text:~/.ssh/config
Host host1
    User user1
    HostName 192.168.0.2
    IdentityFile ~/.ssh/id_rsa

Host host2
    User user2
    HostName 192.168.0.3
    IdentityFile ~/.ssh/id_rsa

Host host3
    User user3
    HostName 192.168.0.4
    IdentityFile ~/.ssh/id_rsa
```

まずここから`Host`を抜き出してみましょう。シェル芸に慣れた人であれば簡単ですね[^shell-geinin]。

[^shell-geinin]: そうじゃない人はごめんなさい。

```bash:Terminal
$ cat ~/.ssh/config | awk '$1=="Host"{print $2}'
host1
host2
host3
```

そして、それを`xpanes`コマンドに渡して`ssh`でログインしましょう。

```bash:Terminal
$ cat ~/.ssh/config | awk '$1=="Host"{print $2}' | xpanes ssh
```

こんな出力が得られるはずです。

```
$ ssh host1                               │$ ssh host2 
                                          │
                                          │
                                          │
                                          │
                                          │
                                          │
                                          │
                                          │
                                          │
------------------------------------------+------------------------------------------
$ ssh host3
                                          
                                          
                                          
                                          
                                          
                                          
                                          
                                          
                                          
```

最初のアニメーションで紹介したように、50台くらいのホストであっても安定してウィンドウを分割してくれます[^myexample]。

[^myexample]: ちなみに私はこのような方法と高解像度のディスプレイを使って100台以上のホストに同時ログインしたことがあります。

### コマンド一覧表からコマンドを並列実行する

使い方に慣れないうちは一旦ファイルにコマンドを記述して、それを標準入力として与えると良いかもしれませんね。

```bash:Terminal
# command-listという、コマンドを記述したファイルを用意
$ cat command-list
top
vmstat 1
watch -n 1 df
while true; do curl -I localhost:8080 && sleep 1 ;done

# -e オプションでそのまま実行
$ cat command-list | xpanes -e
```

出力

```bash:Terminal
$ top                                     │$ vmstat 1
                                          │
                                          │
                                          │
                                          │
                                          │
                                          │
                                          │
                                          │
                                          │
------------------------------------------+------------------------------------------
$ watch -n 1 df                           │$ while true; do curl -I localhost:8080 && sleep 1 ;done
                                          │
                                          │
                                          │
                                          │
                                          │
                                          │
                                          │
                                          │
                                          │
```


### プリキュア49人全員の変身口上

プリキュア[^precure]関連の情報を扱う[Cureutils](http://qiita.com/greymd/items/666a26459aa1bd4cb06c)というコマンドラインツールに[`cure echo`](http://qiita.com/greymd/items/666a26459aa1bd4cb06c#cure-echo)というコマンドがあります。このコマンドを使うことで、プリキュアが変身する時の口上を表示することができます。普通の端末では、せいぜい同時に表示できても数人ですが、`xpanes`を使うことで、プリキュア全員[^precure_all]の変身の口上を一覧して確認することができます。大変便利ですね。

[^precure_all]: TVシリーズに出演したことのあるメインヒロイン全員の数です。なのでキュアエコーは含めません。ごめんなさい。メインヒロインではないので満や薫やキュアゴリラやキュアモフルンは含めません。あしからず。

[^precure]: 日本が世界に誇る国民的アニメ。2017年5月現在「キラキラ☆プリキュアアラモード！」キラッと放送中！


```bash:Terminal
$ cure precures \
| cure tr '[:precure_name:]' '[:girl_name:]' \
| xpanes -c 'cure echo -p {}'
```

![Screen Shot 2017-05-08 at 10.31.30 PM.png](https://qiita-image-store.s3.amazonaws.com/0/68963/50b1957d-04b2-9450-fbc1-2cf009e17111.png)


`cure precures`でプリキュアの一覧を表示
→`cure tr`で名前を英名に変換
→`xpanes`を使って`cure echo`コマンドに噛ませる

2017/05/07現在、テレビに出演したメインヒロインは49人なので、7x7の平方数になり、綺麗に分割できます。



### オウムを並べて Choo Choo TRAINさせる

ここまで真面目な活用例をお見せしてきましたが、ちょっとした遊び用途で使っても楽しいと思います。例えば[`terminal-parrot`](https://github.com/jmhobbs/terminal-parrot)という、楽しげな動きをするオウムを端末に表示するスクリプトを使ってみましょう。

```bash:Terminal
$ yes terminal-parrot | head -n 25 | xpanes -e 
```

![parrot_movie.gif](https://qiita-image-store.s3.amazonaws.com/0/68963/dfda4430-8e9f-44d8-13b7-854e0318e0cf.gif)

### SLをたくさん走らせる

[`sl` コマンド](https://ja.wikipedia.org/wiki/Sl_(UNIX))を使ってみましょう。

```bash:Terminal
$ yes 'sl -l' | head | xpanes -elev
```
![sl_movie.gif](https://qiita-image-store.s3.amazonaws.com/0/68963/db8876d4-a262-34ca-b578-c8419dfda6bb.gif)

# むすび

「その場限りの使い捨てのコマンドでペインを分割する」というコンセプトをもったtmux-xpanesならびに`xpanes`コマンドの紹介をしました。

既存の分割支援ソフトにはない汎用性と機能とコンセプトを持っているので「こんなソフトウェアを待っていた！」という方がいたら喜ばしい限りです。このコマンドの面白い使い方があれば是非教えてください。

また、[ソースはGithubで公開している](https://github.com/greymd/tmux-xpanes)ので、[Wiki](https://github.com/greymd/tmux-xpanes/wiki)の編集、バグの報告や、機能追加の提案、プルリクエストなどもお待ちしています。