14
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

ハンズラボAdvent Calendar 2019

Day 4

シェルスクリプトからコマンド実行(っぽいもの)を抜き出すワンライナー

Last updated at Posted at 2019-12-03

こちらは、ハンズラボ Advent Calendar 2019 4日目の記事になります!

はじめに

最近うまいもの食べすぎで太ってきたyktakaha4です...🐖

Amazon Linuxのセキュリティサポート終了まであと半年となりましたが、みなさま対応は進んでいますでしょうか?
私の所属するCRMチームでは、主にバッチサーバとしての用途でAmazon LinuxのEC2がいくつか稼働しています
以前からバッチ処理のPython化&ECSタスク化も進められていますが、全てのコードを置き換えるには時間がかかるので、並行してAL2のEC2へのプログラム移行に着手しています

弊社はユニケージ開発手法という、シェルスクリプトでシステム開発をおこなう手法を広く採用していた経緯があり、
CRMチームでも、特にバッチ領域についてはbashで書かれた業務ロジックがまだまだ残っています
ユニケージを使用しているサーバには、selfjoin1といった独自コマンドがいろいろインストールされているのに加え、
nkfをはじめとした標準で入ってないコマンドも適宜追加されており、移行の障壁となりそうです...

構築当初(何年も前のことです...)のドキュメントを漁ったり、yumから追うのもよいですが、
実際に使われるコマンドは大抵限られており、また移行の過程で使わなくなったものもあるはずなので、
インストール&動作検証対象を減らすためにも、ソースコード上で利用されているコマンド一覧(とできれば件数)が分かると良さそうです

結論

なんかできました👹

ターミナル(Macの場合)
$ find . -name "*.bash" | xargs ggrep -oP '(^|(?<=;)|(?<=do )|(?<=then )|(?<=`)|(?<=&&)|(?<=\|\|)|(?<=\|)|(?<=\$\())(\s*\w+=\w+\s)?\s*\w+(?=$|[^=])' | awk -F: '{print $NF}' | awk '{print $NF}' | sort | uniq -c | sort -nrk1 | head -5
 500 error_exit <- ※こいつはfunction。引っかかってしまうがしゃーなし
 400 source
 300 cat
 200 basename
 100 plus <- ※こいつはユニケージコマンド

上述の通り、functionなども拾ってしまうため目検は必要ですが、
今回だと 1 lha (こいつ)みたいな結果も得られたのでそれなりに満足です

目で見るのが面倒な方は、移行元のAmazon Linuxでコマンド(っぽい文字列たち)をwhereisにかけるとよいでしょう
更にその結果から抽出したコマンドを移行先のAmazon Linux2でwhereisして、出てこなかったものが要インストール...という考え方でもよいかもしれません

ターミナル
$ cat commands.txt | head -5
000
awk
basename
cat
cd

$ whereis $(cat commands.txt) | head -5
/usr/bin/awk
/usr/bin/basename
/bin/cat
/usr/bin/cd
/bin/cp

解説

ワンライナーだと見づらいので、改行&コメントをつけてみました

整理する
# 現在ディレクトリ配下のbashファイルを抽出
find . -name "*.bash" |
# ファイルごとに、正規表現にてコマンドっぽい文字列を抽出(後述)
xargs ggrep -oP '(^|(?<=;)|(?<=do )|(?<=then )|(?<=`)|(?<=&&)|(?<=\|\|)|(?<=\|)|(?<=\$\())(\s*\w+=\w+\s)?\s*\w+(?=$|\W)' |
# ファイルパス:コマンドっぽい文字列 の形式で出力されるので、コロン区切りで最終フィールドを抽出
awk -F: '{print $NF}' |
# スペース区切りで最終フィールドを抽出(理由は後述)
awk '{print $NF}' |
# カウントのために並び替え
sort |
# 件数カウント
uniq -c |
# 件数順に並び替え
sort -nrk1 |
# 上位5件を表示
head -5

途中、ggrepという馴染みのないコマンドがありますが、こちらはMac環境で上記ワンライナーを実行する際に必要になるものです
今回grepコマンドの正規表現にて肯定先読みと肯定後読みを使用していますが、こちらを利用するために必要な-Pオプション(PCRE準拠の正規表現を使用)が、Macにデフォルトで入っているBSD版のgrepコマンドだと使えないため、追加でインストールしています
Amazon Linux上で実行する場合は、単純にgrepに変更すれば動作するはずです

ターミナル
# インストール
$ brew install grep

# BSD grepはだめ Macにはデフォルトではこちらが入っている
$ grep -V | head -1
grep (BSD grep) 2.5.1-FreeBSD

# GNU grepなら🙆‍♀️
$ ggrep -V | head -1
ggrep (GNU grep) 3.3

本記事のキモとなる正規表現の部分ですが、Debuggexというサイトで可視化してみました

regex.png

仕様としては、大体以下のような感じになるはずです
こちらなども参照しつつ、思いつく限りのコマンド実行のパターンを列挙したつもりですが、抜けやバグがあるかも...
ただ、主だったパターンを抽出できれば十分なので、とりあえず納得して使っています

検出 内容 備考
普通のやつ cat example.txt
1行に複数コマンド cat example.txt; echo $?
パイプして複数コマンド cat example.txt | grep -c "hoge"
制御構文 for f in *; do echo $f; done
if [ -f example.txt ]; then echo "found!"; fi
制御演算子 [ -f example.txt ] && echo "found!"
[ -f example.txt ] || echo "not found..."
コマンド置換 echo "時刻は `date` です"
echo "時刻は $(date) です"
変数定義&コマンド LANG=C sort -u ユニケージで頻出。意味はこちら
× コマンドの引数にコマンド timeout 10 ping
eval 'echo "hello"'
timeout evalしか検出できない
× 変数展開された結果コマンド cmd="whoami"; $cmd 🤗

なお、上記とは別にヒアドキュメントや複数行中の文字列functionaliasなどについても誤検出されます

おわりに

現在は、コマンド調査の結果を元に作成したAL2のDockerイメージ上でスクリプトを実行し、ディレクトリの権限やユーザなどの設定のうち必須そうなものを洗い出しています
ECSになるかDocker on EC2になるかEC2になるかはまだ決めかねていますが、年度末に控えたRI購入イベントまでに決着するよう頑張りたいと思います...!

ハンズラボ Advent Calendar 2019 明日は、yuka_jyoteiさんです!

14
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
14
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?