bashコマンドライン編集と友達になる

## はじめに

### bash コマンドライン編集? なぜ?

今回 bash のコマンドライン編集機能について取り上げますが、要は bash でコマンドを入力するときの編集機能 = bash でコマンドを効率よく入力するための機能、です。

社内向けにハンズオンやったりすると、画面共有して操作見ながら話しすることがありますが、若手とか慣れてない方の CLI 操作を見てると「あーそこはこういうのを知ってればもっと効率よく操作ができるんだけどなあ」というシチュエーションがときどきあるんですね。そういうときは本題が別にあるのでどうこうしないんですが。ただ、自分もほかの人のテクニックを覗き見てどうやってやるのか教えてもらったり、後で知って「あーなんで今までこんな便利なものを知らなかったんだ…」ってなったことがあるので、できれば知ってほしいなあと思うわけです。

コマンド入力なんてね、一日何百回、年間何万回 (?) とかやるわけですよ。作業の効率化…そのインパクトって、作業の重要度と実行頻度によって決まりますが、CLI での操作って実行頻度が多いんですよね。ちょっと操作効率が上がるだけでも余裕が出てくる。ひいては、個々の操作だけじゃなくて作業全体を俯瞰してコントロールする方にリソースを割り振れるわけです。たぶん。


## bash コマンドライン編集

bash でコマンドを入力する際、コマンド入力を補助するための機能がいろいろあるのですが、デフォルトでは emacs のキーバインドをベースにした “emacs mode” になっています。ただね、いま、特に若手で emacs を常用してるという人がどれくらいいるでしょうか? emacs わかると理解しやすいんですが、emacs わからんから bash emacs mode もわからん……になると大変もったいないです。なので、実際の emacs の動作と合わせて bash emacs mode でどんなことができるのかを見ていきます。vi 派の人はごめんね、vi mode については別途調べてください。私は emacs 派なのです。

### 画面構成と表記

このあと、emacs と bash CLI に対して同じキー操作をしてどんな動きになるのかを出していきます。
![screen.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154230/4fe38f6b-bf9d-ce43-f227-008eeb2247cc.png)
画面は上記のように三つに分割されています。①~③ はそれぞれ連動しています。

具体的に言うと、tmux で二つの pane ①② を設定し、① では emacs を起動して `.bash_history` (bash のコマンド履歴ファイル) を開いています。② は通常の bash CLI です。tmux には synchronize-panes という機能があって、キーボードからの入力を複数の pane に同時に送ることができます。つまり、③ ではキーボードからの入力を表示していて、それが ①② に送られ、emacs と bash を同時に操作しています。見たいのは bash なのでフォーカスは ② にあります。そのため ① にはカーソル出ていませんが、カーソル位置で縦横の背景色が変わるのでそれに注目してください。

さて、③ キー入力ですが、以下のルールで表示されます。

- `C-x` : `Control` + `x`
- `M-x` : `Esc` + `x` または `Alt` + `x`
- このほか `Tab` や `Backspace` 等はそれに応じた文字で表示されますが見ればわかると思います。

### Tab補完 (`Tab`)

これはたいていの人が知っていると思う……のと、あとでちょっとしたカスタマイズの方法と合わせて簡単に紹介するので項目だけ挙げておきます。とにかく何かつまったら `Tab` 叩いてみて補完されるかどうか・どんな候補が出るかを見ましょう。

### 履歴の移動 (`C-n`, `C-p`)

まずは基本から。
![C-np.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154230/7cdd259f-83b4-839b-a29f-287afe322d35.gif)
bash は入力したコマンドの履歴を履歴ファイル ( `.bash_history` ) として保持しています。これを pane ① (emacs) で開いています。bash CLI ではその履歴情報をつかって、過去に入力したコマンドをたどっていくことができます。これは `C-p` (`↑`), `C-n` (`↓`) に割り当てられています。

最初なので emacs と bash の動作を説明しましょう。

まず emacs の動作について。emacs ではカーソル移動 (行移動) に対して `C-n`, `C-p` が割り当てられているのですが、これは `C-p`, `C-n` 入力に対してそれぞれ `next-line`, `previous-line` という emacs 内部の関数が呼び出されるように設定されていて、その結果としてカーソルの位置(行)が移動するようになっています (see also: [Moving Point (GNU Emacs Manual)](https://www.gnu.org/software/emacs/manual/html_node/emacs/Moving-Point.html))。なんで `n` とか `p` なのかというと next/previous からだと思います。

:::note info
この後もこういうキーバインド色々出しますが、必ずしも入力キーと機能名が素直につながるわけではありません…
:::

bash のコマンドライン編集の操作は、デフォルトでは “emacs mode” になっていて、emacs の操作を踏襲したキーバインドになっています。こんな感じで「エディタ (emacs mode の場合は emacs) で履歴ファイルを開いたときの動作」との対応関係がわかると bash CLI でのコマンドライン編集がイメージしやすくなります。

### 行内でのカーソル移動: 行頭・行末移動 (`C-a`, `C-e`)

長いコマンドやファイルパスを入力してると、カーソルいったりきたりが面倒だったりしますよね。じつは一発で行頭・行末に移動することができます。emacs では `C-a` (`move-beginning-of-line`), `C-e` (`move-end-of-line`) です。
![C-ae.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154230/e0ef41ca-f5fe-1694-01e1-baae4d02f144.gif)

### 行内でのカーソル移動: 単語単位の移動 (`M-b`, `M-f`)

行頭行末はちょっと飛びすぎる。もうちょっと中間的な移動が欲しいんじゃ…という場合。単語区切り単位での移動ができます。emacs では `M-f` (`forward-ward`), `M-b` (`backward-ward`) です。
![M-bf.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154230/751ded49-8f1a-b785-8019-ace9193fce3c.gif)

### 行内カーソル移動: 文字単位の移動 (`C-b`, `C-f`)

もちろん文字単位(1文字ずつ)の移動もできます。これはカーソルキー (`←`/`→`) でも OK です。emacs では `C-f` (`forward-char`), `C-b` (`backward-char`) です。
![C-bf.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154230/5b032b78-5e1d-0851-de7e-282ca5354fa8.gif)

### 履歴の検索 (`C-s`, `C-r`)

**個人的一押し**です。前に入力したコマンドを探すためにひたすらカーソルキー (`↑`/`↓`)を連打してたり、 `history | grep` で探したりしている人はぜひこれを試してください。

emacs では `C-s` (`isearch-forward`), `C-r` (`isearch-backward`) … “i-search” は incremental search, つまり検索対象文字を入力するたびに逐次ファイル内を検索していく機能のことです。

![C-rs.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154230/c0095c1b-f4b5-1fde-d954-af1004d49d03.gif)

emacs での文字列検索と bash CLI での履歴検索の動作の対応がわかりますか? もうちょっと細かく見ると、検索語入力のプロンプトも連動しているのがわかると思います。

これを使えば、任意のワードで過去履歴 (履歴ファイルに残っている範囲で) を検索できます。断片的な単語でも、行頭から始まっていなくても大丈夫です (途中に含まれる文字列でよい)。emacs の文字列検索の動作がわかっていれば bash での動作も見えてくると思います。

:::note warn
bashで `C-s` を使うには別途設定が必要です。(後述します)
:::

### コマンドライン編集: 行単位の削除とコピー (`C-k`, `C-u`, `C-y`)

ここまでは履歴の検索とカーソル移動の話だったんですが、ここからは編集のほうにうつります。

まずはカーソル位置から行末まで全部を切り取る + コピーして貼り付ける方法です。emacs では `C-k` (`kill-line`), `C-y` (`yank`) です。カーソル位置から行末までを全部切り取るんですが、この時、切り取った文字列を kill-ring に格納します。kill-ring は windows でいうクリップボードみたいなもんだと思ってください (語弊ありそう)。なので kill-ring から取り出して (yank) 貼り付けることができます。
![C-ky.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154230/4890bcc0-e36e-4dca-d0ed-939c4be881b1.gif)
これの逆に `C-u` があって、カーソル位置から行頭までを切り取ることができます。これは emacs には相当するものはありません (標準の機能としては)。
![C-uy.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154230/91c19ce9-04e6-cb5b-849c-a35f7879f326.gif)

### コマンドライン編集: 単語単位の削除とコピー (`M-d`, `C-y`)

カーソル移動と同じく、行単位のコマンドがあれば単語単位のものもあります。emacs では `M-d` (`kill-word`) です。
![M-dC-y.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154230/321357e0-b79e-0823-fbc7-2b04a4106795.gif)

### コマンドライン編集: 文字単位の削除 (`C-d`)

カーソル位置にある文字を削除します。これは kill-ring にはコピーされません。emacs では `C-d` (`delete-char`) です。 `Delete` でも可。
![C-d.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154230/b1de3692-7b20-74ea-2e2a-4ae80f91b83f.gif)
なお、画像は用意していませんが、カーソル位置の一つ前(左)の文字を削除するのは `Backspace` あるいは `C-h` でもできます。

一般的なキーボードだと、 `Backspace` とか `Delete` って割と遠く (キーたたきにくいところ) にあるんですよね。 `C-d`, `C-h`で覚えておくとちょっと楽できたりします。

## readline とその設定でやれること

### readline?

コマンドライン編集のためのインターフェイスで GNU readline というライブラリがあります。bash はコマンドライン編集にこれを使っていますし、bash 以外のソフトウエアでも使用されています。そのため、bash の設定ファイルと readline の設定ファイルが別になっていて、readline の設定は `.inputrc`というファイルを使用します。

### 履歴の前方一致検索

まずは動作を見てみましょう。
![rl-C-np.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/154230/6c5fddfc-b2c4-f535-d5a8-cbeb3489dde7.gif)
`docker-compose` まで入力した後、 `C-n`, `C-p` で「最初に入力済みの `docker-compose` とマッチする履歴の検索」= 前方一致検索をしています。 `C-r`, `C-s` は任意の位置でマッチする文字列を探していますが、こちらは「前方一致」である点に注意してください。ある程度入力するコマンドがわかっていて、細かいパラメータだけ履歴から検索したい、みたいなときに便利です。

これは `.inputrc` に以下のような設定をすることで実現できます。(この例だとカーソルキー `↑`/`↓` には設定していませんがもちろんそれらのキーにも設定できます)

```bash
"\C-n":history-search-forward
"\C-p":history-search-backward
```

### タブ補完動作のカスタマイズ

上の動画 (履歴の前方一致検索) を見てほしいんですが、タブ補完の動作を変えています (最初の `docker [TAB]` で補完候補がどう出力されているかに注目)。今回の話からするとちょっと余談なので興味がある方は調べてみてください。(参考: [Readline - ArchWiki](https://wiki.archlinux.jp/index.php/Readline) )

```bash
set colored-stats On
set visible-stats On
set mark-symlinked-directories On
set colored-completion-prefix On
set menu-complete-display-prefix On
```

## bash の設定

履歴検索 (`C-s`) について :warning: 警告 をつけたところがありました。 `C-s` については、以下の設定を `.bashrc` 等に入れておくか都度設定するかしないと使用できません。

```bash
stty stop undef
```

デフォルトでは、 `C-s` はターミナルへの出力を停止するためのコントロールキー (フロー制御記号) として使用されます。`C-s` を入力するとターミナルの出力が固定されて表示が変わらない状態になります。bash CLI 操作時、何かの拍子でキー入力も何も受け付けない・何も反応しなくなる (ように見える) 状態になったことがありませんか? おそらく `C-s` を入力してしまっていると思います (その時は `C-q` でターミナル出力が再開する)。上記のコマンドはこのフロー制御記号を無効化するものです。

フロー制御自体は、大量のログをずっとターミナルに流していて、表示を一時的に止めて確認したい…といった用途で使用するようです。ですが、いまターミナル上でそうしたコントロールが必要なケースはほとんどないと思いますし、この動作を知らずについうっかり入力してしまった際の困惑のほうが問題でしょうから、無効化しておいてよいと思います。こういうものがあるってことを知らないと対応できないんですよね……。

## おわりに

bash のコマンドライン編集機能について紹介しました。これ自体は何番煎じだ……というネタではあるんですが、emacs の動作との対比を見れるようにしてみました。最初にも触れた通り emacs 触っているとわりと素直に受け入れられるんですが、イマドキだと最初に触るエディタは emacs ではないかなーというのもあり、こういう見せ方で紹介してみるのも意味があるだろうと。

こういった環境カスタマイズをしている人なら [peco/peco](https://github.com/peco/peco) でいいじゃんって人もいると思います。もちろんそれで OK です。ただ、システム運用とかにかかわっていると、自分に管理権限がないサーバで作業しないといけなかったり、あるいはコンテナベースのツールがうまく動かなくてコンテナで shell を実行して調査することがあったり、個人の思いだけでホイホイいじれないノードを操作することがありますよね。bash は多くの Linux ディストリビューションで default shell になってますし、こうした基本機能を知っておいて損はしないと思います。

あと readline もいろんなツールが使ってるんですよね。個人的な話ですが、最近だと `python -i` (対話モード) で複数行のペーストがうまく動かなくてなんでじゃと思ったら readline 設定によるものだったってこともありました (参考: [Python: REPL に複数行をペーストしたときの挙動が変わって困った件について - CUBE SUGAR CONTAINER](https://blog.amedama.jp/entry/python-broken-pasting))。覚えておくと他のところで使えたりするかもしれませんよ?

コマンドライン編集を覚えて、よい CLI 生活を！

## 参考資料

- [O'Reilly Japan - 入門 bash 第3版](https://www.oreilly.co.jp/books/4873112540/)
- [col-highlight:現在桁をハイライトさせるお好み3手段 - るびきち「新生日刊Emacs」](http://emacs.rubikitch.com/col-highlight/) : 今回は col-highlight + hl-line の合わせ技です
- [tmuxで複数サーバの同時オペレーション – NaviPlus Engineers' Blog](https://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/)
- [Screenkey、デスクトップで押すキーを表示するアプリケーション| Ubunlog](https://ubunlog.com/ja/screenkey-mostrar-teclas-pulsadas/)