意外と知らない便利なShellに関する10の小技


はじめに
========

最近、サーバ上での作業の他にも、
JenkinsやDockerなどでもShellスクリプトを書いたりする機会が増えてきていると思います。

社内の人と話していても意外と知られてないけど便利な小技をまとめてみました。

小技
=====

１．ファイルの中身を置換するワンライナー
-----------------

JenkinsやDockerでちょいちょいと既存の設定ファイルを置換するときに便利です。
こんな感じでよく使っています。

```shell
perl -i -pe 's/正規表現１/置換/' 対象ファイル
```

テストするときは `-i` をなしで実行して結果を確認するようにして、
実際に実行する場合に `-i` をつけると良いです。

### 例

```shell
# DISABLE PAM
perl -i -pe 's/^UsePAM yes/UsePAM no/' /etc/ssh/sshd_config 
```

２．Shellの変数の中身を置換して利用する
----------------------


`${変数名/置換前/置換後}` という書き方ができます。
こんな感じになります。

```shell
% X=abcdefg.json 
% echo ${X/.json/.txt}
abcdefg.txt
```

### 例

これを使うとファイルの拡張子の一括置換とかに便利です。

```shell
% touch {1..5}.json
% ls
1.json	2.json	3.json	4.json	5.json
% for filename in `ls *.json`; do; mv $filename ${filename/.json/.txt}; done 
% ls
1.txt  2.txt  3.txt  4.txt	5.txt
```


３．Shellの変数のデフォルト値を設定する
--------------------

Shellスクリプトで環境変数を経由して値の入力とするケースは多いと思いますが、デフォルト値を指定しておきたいこともよくあります。
その場合、`変数=${変数:-デフォルト値}` という書き方が便利です。


```shell
% A=${A:-hogehoge}
% echo $A
hogehoge            # 最初は何も値がないので hogehogeになる
% A=${A:-fugafuga}  # 同じ変数にデフォルト値 fugafuga をセットする文を書いても
% echo $A
hogehoge            # 既にhogehogeがあるので変更されない
```

４．途中でエラーが発生したら終了するようにする
----------------

まじめなShellスクリプトでは、途中でエラーが発生したら終了してしまう方が望ましいことがあります。
途中のSetupスクリプトで実はエラーが発生していたのに、全体では正常終了に見えてしまって不具合に気が付かなかった、、、とかいうことを防ぐためです。

そういうときは `set -e` を使うと良いです。

### 例

例えば、以下の様なスクリプトを実行すると、

```shell:a.sh
echo first
er
echo second

set -e

echo third
er
echo 4th
```

以下のように、最初の `er` というコマンドはエラーですが継続して処理が進みますが、
`set -e`した後だと `er`でエラーになるので、その後の `echo 4th`が実行されません。

```shell
% sh a.sh
first
a.sh: line 2: er: コマンドが見つかりません
second
third
a.sh: line 8: er: コマンドが見つかりません
```

５．catでファイルを作るとき
-----------

ヒアドキュメントを使うときに、些細なことですが、 `cat <<EOF > /path/to/file` という風に書くことができます。
なんとなくまとまって見やすいので気に入って使っています。

### 例
```shell
% cat <<EOF > /path/to/file
DB_HOST=....
DB_PORT=....
EOF
```

ヒアドキュメント内で`$`で変数を展開したくないときには `'EOF'`という書き方ができます。

```shell
% cat <<'EOF' > x                                                                                                                                                                 
echo $A
EOF
% cat x
echo $A
```

６．実行ファイルの絶対DIRを取得する
------------

Shellスクリプトの実行時にそのファイルの絶対PATHがほしい時があります。
そういう時には、こういう書き方ができます。

`THIS_DIR=$(cd $(dirname $0); pwd)`

### 例

```shell:x.sh
THIS_DIR=$(cd $(dirname $0); pwd)

echo $THIS_DIR
```

```shell
% sh x.sh
/home/k_morishita/tmp
```
### 追記：別の方法

コメントで @heliac2000 さんから教えていただきました。ありがとうございます！
そのまま追記しておきます。

`realpath`, `reallink` コマンドがインストールされていれば以下の様にして絶対パスを得ることができます。


```shell
$ pwd
/work/tmp
$ touch a
$ readlink -e a
/work/tmp/a
$ realpath a
/work/tmp/a
```


７．ファイルの１行目を削除して出力する
-----------

先頭のN行、末尾のN行を出力するならば、`head`, `tail` などが使えますが、
N行名を出力から除外する、というのはわりと悩んでました。

他にも方法があった気がしますが、

`sed 1d ファイル名` 

１〜３行目なら 

`sed 1,3d ファイル名`

とかけば実現できます。

８．Shell UIの小技
--------------

### 履歴を検索

`CTRL + R` でコマンド履歴から文字列検索することができます。
これを知っているかどうかでかなり作業効率が変わりますよ。

### 前にいたDIRにcdする

```shell
% cd -
```

とすると、その前にいたDIRにcd することができます。

### 前の最後の引数を補完する

`ESC` -> `.`(ピリオド) の順にキーを押すと、履歴の最後の引数が補完されます。
何度も押すことで履歴をさかのぼります。

```shell
% echo hoge
% echo fuga
% echo [ESC -> . と押す] → fuga -> hoge と順に表示される
```

意外と便利です。

９．定期的にコマンド結果を出力する
---------------

例えば、特定のDIRにファイルができるのを目視でチェックしたいということがあります。
気が向いたら `ls` を打つとかでも良いですが、

```shell
% watch [-n 秒数] ls
```

とするだけで、デフォルトで２秒間隔でコマンドを実行してくれます。

１０．よく使うコマンド群を関数として定義しておく
-------------

よく使うコマンドは、 .zshrc や .bashrc などに関数として登録しておきましょう。
例えば、Docker関連だとこういうのが結構重宝します。

```shell
# 終了したDockerコンテナを全て削除する
rm_all_docker_containers() {
    for id in $(docker ps -a | grep 'Exited ' | awk '{print $1}')
    do
        docker rm $id
    done
}

# 名前が無いDocker Imageを全て削除する
rm_all_docker_none_images () {
   docker images | grep '^<none>' | awk '{print $3}' | xargs docker rmi
}

# 実行中のDockerコンテナを全て停止する
stop_all_containers() {
  ids=$(docker ps | grep -v 'CONTAINER ID' | cut -d' ' -f 1 )
  for id in $ids
  do
    docker stop $id
  done
}
```


さいごに
======

他にもあった気がするけど、思い出したら付け足そう。。

（追記）
「perl とか sed とか Shellじゃないじゃん！」というごもっともなツッコミは甘んじて受けさせて頂きます（＞＜
ちょっとタイトルを緩くしました。細かいこと、キニシナイで。