背景
ログ調査などの際に複雑なワンライナーを考案して、スクリプト化するほどじゃないけど覚えておきたい、ということはありませんか?
かつ、(Linuxの機能を使うならばエイリアスや関数を設定ファイルに登録しておくのがもちろん素直ですが)コマンドを打つサーバが本番環境だったり共有のサーバだったりして、.bashrcなどの設定ファイル類には手を触れたくない、ということはありませんか?
私の場合はよくあります。そしてそういう時は、無難な解決策として自分の個人端末上のテキストファイルなどにコマンドをメモっておいて、コピペして使っています(現在進行形)。
同じことをしているサーバーサイドエンジニアは少なくないのではないでしょうか。
しかし、いちいちコピペをしたり、場合によってはコマンドの一部を手で書き換えてから実行したり、というのはやはり不便です。そこでエイリアスでも関数でもない第3の選択肢として、コマンドを保存しておいて対話的に呼び出せるスクリプトを作ってみました。
※bashの構文であるselectを使っています。
スクリプト本体
#!/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
使い方
- 使いたいコマンドをスクリプト冒頭のCOMMAND_LISTで指定したリストファイルに登録。
- スクリプトを実行。
コマンドのリストへの登録と実行(例1)
リストファイルのパスは固定で、スクリプトと同じディレクトリに配置されている"benri_command.tsv"です。
(命名がダサいと思う方は適当に変更してください)
benri_command.tsvに以下のコメント文の要領でコマンドを登録します。
# タブ区切りで3つのフィールド(コマンドの説明、コマンド、変数名)を入力
# 変数が複数ある場合は変数名フィールド内にカンマ区切りで入力
# 固定のコマンドの場合変数名フィールドはなくてもよい。前のタブ文字も不要
# コマンドの説明は、コマンド検索のキーとなるため他の行と被らないようにする
# '#'で始まる行はスクリプト側で無視している(コメント扱い)
こだま echo message message
スクリプトを実行すると以下のようになります。
$ ./stored_oneliner.sh
実行したいコマンドの番号を入力してください
1) こだま
#? 1
echo message
変数(message)の値を設定してください
message ... aaa
echo aaa
このコマンドを実行します。よろしいですか?[y/n] y
--- 出力 ---
aaa
例2: 固定のコマンドの場合
変数がないコマンドを登録した場合は以下のような挙動になります。
こだま echo message message
固定のこだま echo "Hello World!"
$ ./stored_oneliner.sh
実行したいコマンドの番号を入力してください
1) こだま
2) 固定のこだま
#? 2
echo "Hello World!"
このコマンドを実行します。よろしいですか?[y/n] y
--- 出力 ---
Hello World!
例3: 変数が複数ある場合
変数を複数(カンマ区切りで)登録するとこうなります。
こだま echo message message
固定のこだま echo "Hello World!"
ダブルのこだま echo A B A,B
$ ./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
実用シーン
実用的には下記のような類のログ解析コマンドを登録しておくと便利だと思います。
当日の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)の全文を載せておきます)
# *** ワンライナーのリスト ***
# タブ区切りで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