ほんのちょっとの工夫だけで、一歩便利なシェル生活(とその例)

# シェル、だいじですよね

シェル、書いてますか！？僕はほぼ毎日書いてます！
今や職業プログラマーでも、DevOpsの名の下にサーバ運用をしたり、
開発だけする身であっても、DockerやGitをフル活用していくのが当たり前になってきていますよね！

そこで覚えたいコマンドの数々なんですが、GitだとかDockerだとか、どうにも面倒なコマンド多いですよね :crying_cat_face: 

そこで今回は、できるだけ複雑なことをしないでちょっとの工夫で幸せになれる、僕がよく使うコマンドをいくつかご紹介したいと思います！

覚えることはたった二つです！

- alias
- サブシェル

シェルばりばりだぜー！って人でもないと、意外と使ってる人が少ないこれらの機能について紹介してみたいと思います！これで今日からちょっぴり幸せ！

## これがやりたいだけなのに・・・

### 例えばGitあるある

- いらないファイルを`rm`で沢山消したら、大量の`deleted ～～`が発生！しかももうファイル消しちゃってるからBashだと補完も効かない・・・ :scream: 
- 今日は沢山ブランチ切って沢山PRして沢山マージされたぞ！…あれ？ローカルに大量の不要なブランチが…まとめて消したいなぁ。
- `git stats` あっ！ `git statsu` あれっ `git sttaus` …あぁもうっ！ :anger: 

### 例えばDockerあるある

- 今起動中のDockerコンテナにログインするよ！えーっとまず`docker ps`でコンテナのID確認して、コピペして(めんどくさいなぁ、なんでマウス使ってるんだろ…)、`docker exec`に貼り付けてって手順だったな、よしよし、じゃあ早速 `docker ps` …あぁ`sudo`つけてなかった！もうっ！ :sob: 
- 最新のコンテナのログを見たいなぁ、えーっと`docker ps`して(以下同文

### 例えばJava/Mavenあるある

- 調子悪いから一旦綺麗にしてからインストールしよう、テストはスキップで。えーとコマンドなんだっけ、あ、あったあった。 `mvn -U clean install -Dmaven.test.skip=true` って長いよ！めんどくさいよ！わざわざ「コマンド.txt」にメモってあるの開いていつもそこからコピペしてるよ！ :tired_face: 

## alias(エイリアス)使いましょう:angel:

エイリアスとは、簡単に言えば**コマンドにニックネームを付ける**みたいなイメージです。
例えば上記の `git status` を何度も打ち間違えてしまう慌てんぼうさんには、以下のようなエイリアスがオススメです。

```bash
alias gs="git status"
```

これを、お手元の`~/.bashrc`に書いておくだけで、次にシェルを起動したときからは、`git status`を`gs`と打つだけで実行できるようになります。

Docker系のコマンドのように、必ず`sudo`をつけて実行しなければならないようなものも、毎度毎度書くのが面倒なら

```bash
alias docker="sudo docker"
```

等としておけば、`docker`と打つたびに毎度sudoを自動でつけてくれます(でもコマンドミスやセキュリティには注意！)

エイリアスは、コマンドの内容を前方一致で好きなだけかけるため[^1]、例えば上のmavenの例も・・・

```bash
# 例えば maven install test skip を略して mits にしてみるとか
alias mits="mvn -U clean install -Dmaven.test.skip=true"
```

これでこの長くてうんざりするコマンドも、次から`mits`で一発です。
「コマンド.txt」は窓から放り投げてやりましょう。

[^1]: zshとかだと、グローバルエイリアスといって、コマンドの途中部分だけをaliasにすることなどができたりします。例えば最後の一番覚えにくそうな `-Dmaven.test.skip=true` の部分だけをエイリアスにしてあげたりすることができるようになり、汎用性が高まります。興味のある方はzshを是非使ってみてくださいね！補完も優秀でとってもオススメです！

## サブシェルを使いましょう:angel:

さてさて、あるあるの例でご紹介した中の、残りのケースはこのサブシェルで解決できます。

サブシェルとは、シェルの中でもう一つ子シェルを立ち上げて、そこでコマンドを実行した結果を、呼び出し元の親に返す代物です。
サブシェルは`$()`のカッコの中や、``` `` ``` (バッククォート記号)で囲った中に、いつもどおりのシェルコマンドを入れることで実現できます。``` `` ```の方がお手軽そうに見えますが、`$()`であれば、サブシェルの中にサブシェルをネスト(入れ子)したりできるので、覚えておくといいですよ！

…と、ちょっと説明だと難しいですね。実例で紹介した方が早そうなので、早速先ほどの問題を解決してみましょう。

### rmで消したファイルを、git上からもちゃんとdeleteしたい。

通常は`git rm ～～`でファイルを消せば問題ないのですが、ついうっかり普通に`rm -rf ～～`とかで消してしまうこと、ありますよね？僕はよくあります。というか毎回やるといっても過言ではないです :stuck_out_tongue_closed_eyes: 

これらをコミットしたいとき、一々`git status`で出てきた結果をコピペしたり頑張って手打ちしたりして`git rm`に渡して一つずつ消す・・・面倒なことこの上ありません。そこで、サブシェル使って一発解決です！:+1:

```bash
git rm $(git ls-files -d)
```

これで、「git管理されてるはずなんだけど、ローカルのファイルシステム上にはもう存在してないファイル」をまとめて消せます！

#### ちょっと解説

簡単に言うと、`git ls-files -d`という、git管理されたコマンドを列挙するだけのコマンド(-dをつけると削除されたファイルだけを一覧する)を実行し、その結果を`git rm`に渡してるだけなんです。このコマンドは、例えば実際にはこんな感じに展開されて実行されます。

```bash
git rm ./a/hoge.txt ./a/fuga.txt ./a/piyo.txt ./b/foo.txt ./b/bar.txt ./b/baz.txt ./b/qux.txt
```

上の例では沢山のファイル名が列挙されていますが、つまりこれが`git ls-files -d`の実行結果であるというだけの話なので、あまり難しく考える必要はありません。

じゃあ同じように別のコマンドも解決してみましょう。

### 沢山増えちゃったブランチを一気に綺麗にしたい！

これも簡単です、以下のコマンドでOK！

```bash
git branch -D $(git branch)
```

`git branch`の結果をそのまま`git branch -D`に渡してしまうという荒業です！
これで今自分がcheckoutしているブランチ以外の全てのローカルブランチ(リモートブランチには影響がないところがポイント！)を消すことができます。ちょっと怖いなって方は`-D`を`-d`に変えれば、まだ派生元にマージされていないようなブランチはエラーが出て消さないでおいてくれるので、最初はそっちで試してみるのもいいかもしれません。

でも一応やる前には、ちゃんととっておきたいブランチが他に無いか確認してからにしましょうね！ :relieved: 

### 起動中の(最新の)Dockerコンテナにログインしたい

普通にやると、例のとおり、`docker ps`で表示されるコンテナのIDをコピーなりしておいて、`docker exec -it {ID} /bin/bash`のような感じで、IDを貼り付けたりしますね。でもこれは以下のコマンドでOKです。

```bash
docker exec -it $(docker ps -lq) /bin/bash
```

もうお分かりかと思いますが、`docker ps -lq`がサブシェルですね！このコマンドは「最後に作成されたコンテナのIDのみを出力する」というものになります。つまりそれをいつもどおり `docker exec -it {ID} /bin/bash` に渡してあげているだけです。こうすると、このコマンドだけ覚えておけば、もう一々`docker ps`を打って、目で見てコピペしなくて良くなります。

#### でも一つだけ注意

勘のいい方なら、ここで「さっきのaliasと組み合わせればもっと簡単なんじゃ？」と思うかもしれませんが
実はサブシェルとエイリアスはあまり相性がよくありません:cry:

サブシェルは多くの場合、`.bashrc`等、そのシェルで一度だけ読み込まれるようなファイルに記述するので、コマンドを打つたびには再実行されません。そのため、サブシェルの中の結果は、いつも最初に定義した(読み込んだ)時の結果だけになってしまい、コンテナIDなどの動的に変わる値を使うのはちょっと難しいです。そういう場合は`function`を定義しておくと、呼ばれる度に新たに実行されるためこの問題を解決することができます(詳しくはシェルのfunctionでぐぐってみてください！)。

## まとめ

サブシェルとエイリアス、べんりです！！ :sparkles:

できるだけ初心者の方にも分かりやすいように書いたつもりでしたが、最後の方はちょっと難しかったかな・・・ :sweat_smile: 
ここに書いてあるようなコマンドは一例ですが、こんな風にちょっと工夫するだけで、面倒だった処理がいきなり簡単になって快適になったりするところがシェルの大きな魅力だと思っています！皆もシェル好きになろうよ！怖くないよ！！

…あ、うそ、たまにはちょっと怖いかもｗ

間違いや説明不足などありましたら、お気軽にコメントください:bow:

では皆さん良いシェルライフを :musical_note: