この記事は、 CAMエンジニア Advent Calendar 2019 22日目の記事です。
昨日は @gucciNaさんのLottieで少し遊ぶでした。
はじめに
こんにちは。cotsupaです。
この記事では、
Shell Scriptで複数ファイルから文字列を検索するコマンド(findgrep.sh)を作る事を通して、
簡単なShell Scriptの構文を学ぶ事が出来たので、出来るだけ詳細な説明をいれて、まとめました。
とりあえず完成形はこちら
#!/bin/bashpattern=$1directory=$2name=$3#第2引数(起点ディレクトリ)が空文字列ならデフォルト値として.(カレントディレクト)を設定//②if[-z"$directory"];then
directory='.'fi#第3引数(検索ファイルパターン)が空文字列ならデフォルト値として'*'を設定//②if[-z"$name"];then
name='*'fi#引数がない場合はエラーメッセージを表示//③if["$#"-eq 0 ];then
echo"Usage: `basename$0` PATTERN [PATH] [NAME_PATTERN]"exit 1
fi#検索ディレクトリが存在しない場合はエラーメッセージを表示//③if[!-d"$directory"];then
echo"`basename"$0"`: ${directory}: No such directory" 1>&2
exit 2
fi#対象ファイルから文字列を検索//①
find "$directory"-type f -name"$name" | xargs grep-nH"$pattern"使用方法
「検索パターン」、「対象始点ディレクトリ」、「対象ファイル」を引数に指定する事で、
始点ディレクトリに含まれるファイル全てを対象に検索を行います。
// ./配下の'*.text'に該当するファイル内でhogeを検索
$ findgrep.sh hoge . '*.txt'
./doc/example.txt:15:hogehogehoge
./doc/example.txt:121:hoge
./README.text:185:(hoge)
スクリプトの解説
上記のスクリプト内で番号を振った順に解説していきます。
前提として、Shell Scriptでは特殊パラメータが存在し、今回は下記を使用して変数を定義しています。
| 特殊パラメータ | 意味 |
|---|---|
| \$0 | 実行しているScriptのファイルパス |
| \$1〜\$9 | 引数(\$1は第一引数、$2は第二引数...) |
| \$# | Scriptに与えた引数の数 |
1, 引数で指定した対象のファイルから文字列を検索
#対象ファイルから文字列を検索//①
find "$directory"-type f -name"$name" | xargs grep-nH"$pattern"findコマンドとxargsコマンドをパイプライン演算子(|)で繋げて実行しています。
パイプライン演算子
- コマンドの標準出力を別のコマンドの標準入力に繋ぐことでコマンドの連携をさせる演算子
findコマンド
- 指定ディレクトリからファイルを検索し、出力するコマンド
-type fでファイルのみを指定、-name "$name"で第三引数のファイル名を指定しています。
xargsコマンド
xargs [実行したいコマンド]という形式で実行し、[実行したいコマンド]が標準入力から受け取ったリストを引数として実行します。- 今回はfindコマンドの出力結果をgrepコマンドが引数として実行しています。
grepコマンド
- 引数のファイルから文字列を含む行を出力します(出力結果を絞って表示する際によく使います)
-nで検索結果の行数を出力し、-Hで引数にファイルが一つしか指定されなくてもも必ずファイル名を出力します。
また、"$directory"など変数をダブルクォーテーションで囲うことで変数展開しています。
(詳しくはCAMエンジニア Advent Calendar 2019 18日目shellの基礎構文)
2, 引数が指定されなかった場合の条件分岐
#第2引数(起点ディレクトリ)が空文字列ならデフォルト値として.(カレントディレクト)を設定//②if[-z"$directory"];then
directory='.'fi#第3引数(検索ファイルパターン)が空文字列ならデファルト値として'*'を設定//②if[-z"$name"];then
name='*'fi「対象始点ディレクトリ」、「対象ファイル」は必要な場合のみ指定できるように制御構文を使用してそれぞれデフォルトの値を設定しておきます。
- 制御構文
ifとthenを使用して条件分岐を記述-zは文字列長が0なら真となります。
(制御構文、シングルクォートによる変数展開の詳細はCAMエンジニア Advent Calendar 2019 18日目shellの基礎構文)
3, エラーメッセージの表示
#引数がない場合はエラーメッセージを表示//③if["$#"-eq 0 ];then
echo"Usage: `basename$0` PATTERN [PATH] [NAME_PATTERN]"exit 1
fi#検索ディレクトリが存在しない場合はエラーメッセージを表示//③if[!-d"$directory"];then
echo"`basename"$0"`: ${directory}: No such directory" 1>&2
exit 2
fi制御構文を使用して、引数がない場合と存在しないディレクトリ名が指定された場合にエラーメッセージを出力させます。
1>&2では出力リダイレクトの記法で標準出力(1番)を標準エラー出力(2番)と同じにする指定です。
一般的なLinuxコマンドはエラーメッセージを標準エラー出力に出力するため、このコマンドもそれに従います。そうする事で、パイプラインなどで別のコマンドに接続する際に支障がなくなります。
(他にも標準出力をファイルにリダイレクトした際でも標準エラー出力は画面に出力されるため見逃さないなど利点があります。)
また、ここではbasenameコマンドを使って、ファイル名を出力しています。
- basenameコマンド
- 引数のパス部分を取り除いて、ファイル名を取り出すコマンド
まとめ
これまで、ほとんどShell Scriptに触れてこなかったので、
実際に作って試してみると単純なロジックでも引っかかる部分が多かったです。
最後に、Shell Scriptを書く際に$ bash -x [コマンド]でデバックモードになるので効率よくScriptを作成できるみたいです。もっと早く知りたかったです。