説明文付きで予め保存しておいたワンライナーを呼び出すスクリプト（bash）

### 背景

ログ調査などの際に複雑なワンライナーを考案して、スクリプト化するほどじゃないけど覚えておきたい、ということはありませんか？
かつ、（Linuxの機能を使うならば[エイリアスや関数を設定ファイルに登録しておく](https://qiita.com/belion_freee/items/528ff57b920d559abf3d)のがもちろん素直ですが）コマンドを打つサーバが本番環境だったり共有のサーバだったりして、.bashrcなどの設定ファイル類には手を触れたくない、ということはありませんか？

私の場合はよくあります。そしてそういう時は、無難な解決策として自分の個人端末上のテキストファイルなどにコマンドをメモっておいて、コピペして使っています（現在進行形）。
同じことをしているサーバーサイドエンジニアは少なくないのではないでしょうか。

しかし、いちいちコピペをしたり、場合によってはコマンドの一部を手で書き換えてから実行したり、というのはやはり不便です。そこでエイリアスでも関数でもない第３の選択肢として、コマンドを保存しておいて対話的に呼び出せるスクリプトを作ってみました。

※bashの構文であるselectを使っています。

### スクリプト本体
```stored_oneliner.sh
#!/bin/bash

# コマンドを登録しているTSVファイルのパス
readonly COMMAND_LIST='./benri_command.tsv'

# コマンドの選択肢を表示し、対話的に選択
echo '実行したいコマンドの番号を入力してください'
echo

select description in $(grep -v '^#' $COMMAND_LIST | awk -F '\t' '{print $1}'); do

  command=$(grep -m 1 "^$description\t" $COMMAND_LIST | awk -F'\t' '{print $2}')
  variables=$(grep -m 1 "^$description\t" $COMMAND_LIST | awk -F'\t' '{print $3}')
  break

done

# 選んだコマンドの確認
echo
echo $command
echo

# TSV内に変数(プレースホルダ)の設定がされている場合は、その値(置換文字列)の設定に進む
if test -n "$variables"; then

  echo "変数($variables)の値を設定してください"

  OLD_IFS=$IFS
  IFS=','
  for v in $variables; do
    echo -n "$v ... "
    read ans
    command=${command//$v/$ans}
  done
  IFS=$OLD_IFS

  # 変数への代入後のコマンド文字列の確認
  echo
  echo $command
  echo

fi

# コマンド実行前に最終確認
echo -n 'このコマンドを実行します。よろしいですか？[y/n] '
read ans
echo

# y(es)なら実行
if test "$ans" == 'y'; then

  echo '--- 出力 ---'
  bash -c "$command"

fi
```

### 使い方

1. 使いたいコマンドをスクリプト冒頭のCOMMAND_LISTで指定したリストファイルに登録。
1. スクリプトを実行。

#### コマンドのリストへの登録と実行（例1）
リストファイルのパスは固定で、スクリプトと同じディレクトリに配置されている"benri_command.tsv"です。
（命名がダサいと思う方は適当に変更してください）

benri_command.tsvに以下のコメント文の要領でコマンドを登録します。
```benri_command.tsv
# タブ区切りで3つのフィールド（コマンドの説明、コマンド、変数名）を入力
# 変数が複数ある場合は変数名フィールド内にカンマ区切りで入力
# 固定のコマンドの場合変数名フィールドはなくてもよい。前のタブ文字も不要
# コマンドの説明は、コマンド検索のキーとなるため他の行と被らないようにする
# '#'で始まる行はスクリプト側で無視している（コメント扱い）
こだま	echo message	message
```

スクリプトを実行すると以下のようになります。

```terminal
$ ./stored_oneliner.sh 
実行したいコマンドの番号を入力してください

1) こだま
#? 1

echo message

変数(message)の値を設定してください
message ... aaa

echo aaa

このコマンドを実行します。よろしいですか？[y/n] y

--- 出力 ---
aaa
```

#### 例2: 固定のコマンドの場合
変数がないコマンドを登録した場合は以下のような挙動になります。
```benri_command.tsv
こだま	echo message	message
固定のこだま	echo "Hello World!"
```
```terminal
$ ./stored_oneliner.sh 
実行したいコマンドの番号を入力してください

1) こだま
2) 固定のこだま
#? 2

echo "Hello World!"

このコマンドを実行します。よろしいですか？[y/n] y

--- 出力 ---
Hello World!
```

#### 例3: 変数が複数ある場合
変数を複数（カンマ区切りで）登録するとこうなります。
```benri_command.tsv
こだま	echo message	message
固定のこだま	echo "Hello World!"
ダブルのこだま	echo A B	A,B
```
```terminal
$ ./stored_oneliner.sh 
実行したいコマンドの番号を入力してください

1) こだま
2) 固定のこだま
3) ダブルのこだま
#? 3

echo A B

変数(A,B)の値を設定してください
A ... Pen Pineapple
B ... Apple Pen

echo Pen Pineapple Apple Pen

このコマンドを実行します。よろしいですか？[y/n] y

--- 出力 ---
Pen Pineapple Apple Pen
```

### 実用シーン
実用的には下記のような類のログ解析コマンドを登録しておくと便利だと思います。
```benri_command.tsv
当日のTomcatのアクセスログでURLごとの呼び出し回数をカウント	awk '{count[$7]+=1}END{for (url in count) print url, count[url]}' /usr/local/bin/tomcat/logs/localhost_access_log.$(date +%Y-%m-%d).txt | sort
当日のTomcatのアクセスログから指定したURL,HTTPステータスコードの行を出力してlessで見る	awk '$7 == "URL" && $9 == STATUS' /usr/local/bin/tomcat/logs/localhost_access_log.$(date +%Y-%m-%d).txt | less	URL,STATUS
```
（実行結果は省略）

スクリプトには最低限欲しい機能だけ実装していますが、改善の余地もありそうですね。
（コマンドをどんどん登録していくと選択肢リストが長くなるので、リストファイル（TSV）自体も選択肢から選べるようにするとか、コマンドの登録作業自体が面倒になってくるのでコマンド登録用オプションを追加するとか）

ご参考になれば幸いです。
（最後に、一応、サンプルのコマンドリスト（TSV）の全文を載せておきます）

```benri_command.tsv
# *** ワンライナーのリスト ***
# タブ区切りで3つのフィールド（コマンドの説明、コマンド、変数名）を入力
# 変数が複数ある場合は変数名フィールド内にカンマ区切りで入力
# 固定のコマンドの場合変数名フィールドはなくてもよい。前のタブ文字も不要
# コマンドの説明は、コマンド検索のキーとなるため他の行と被らないようにする
# '#'で始まる行はスクリプト側で無視している（コメント扱い）
こだま	echo message	message
固定のこだま	echo "Hello World!"
ダブルのこだま	echo A B	A,B
当日のTomcatのアクセスログでURLごとの呼び出し回数をカウント	awk '{count[$7]+=1}END{for (url in count) print url, count[url]}' /usr/local/bin/tomcat/logs/localhost_access_log.$(date +%Y-%m-%d).txt | sort
当日のTomcatのアクセスログから指定したURL,HTTPステータスコードの行を出力してlessで見る	awk '$7 == "URL" && $9 == STATUS' /usr/local/bin/tomcat/logs/localhost_access_log.$(date +%Y-%m-%d).txt | less	URL,STATUS
```