Mastering Shell Scripting: Expert Guidance
You are an expert shell scripting and command-line tutor. Your job is to help me master shell scripting, one-liners, and practical command-line techniques for my work.
In this thread, I will provide shell commands or topics that I would like to learn. For each one, please provide a comprehensive explanation that is thorough enough that I won’t need to consult any other resources. The explanation should cover:
- Core functionality and purpose
- All options and features (except extremely obscure ones), with clear descriptions
- Examples that cover every single option or feature you provided above, mixing basic to advanced, each with expected output or behavior
- Common use cases and best practices
- Potential pitfalls or gotchas to avoid
For all features and examples:
-
Mark any non-POSIX features with [Non-POSIX]
-
Specify the shell name for shell-specific features (e.g., [bash], [zsh], [ksh], [fish], etc.)
-
Tag implementation/platform differences if needed (e.g., [GNU], [BSD], [BusyBox], [macOS], [Linux], etc.)
Include at least 5 exercises of varying difficulty levels with solutions at the end.
Please provide complete, self-contained explanations in each response (don’t split them across multiple messages). For this first response only, please confirm that you understand these instructions without providing examples. From the second message onward, provide full explanations with examples and outputs. Please reply in Japanese.
承知いたしました。
次回以降の回答では、ご指示のとおり各シェルコマンドやトピックについて、以下の点を網羅した詳細な解説を日本語で提供いたします。
- コア機能と目的 – コマンドやスクリプトの基本的な役割と用途
- 主要なオプションと機能 – (極端に特殊なものを除く)すべてのオプションや機能について、それぞれの明確な説明
- 具体例と出力 – 上記の各オプションや機能を網羅するような基本から高度な例を示し、期待される出力や動作を解説
- 一般的な使用例とベストプラクティス – 実務での典型的な使い方や効率的な利用法
- 注意点や落とし穴 – 利用時に陥りやすい誤りや見逃しがちなポイント、およびそれらの回避策
また、解説内では以下の表記規則に従います:
-
POSIX規格外の機能には [Non-POSIX] と明記します。
-
特定のシェルに依存する機能にはシェル名(例: [bash], [zsh], [ksh], [fish])を付記します。
-
実装やプラットフォームによる違いがある場合は [GNU], [BSD], [BusyBox], [macOS], [Linux] などのタグで明示します。
さらに各トピックの最後には、理解を深めるため難易度の異なる5問以上の練習問題を用意し、その解答と解説も添えます。
以上の方針で、すべての説明を完全に自己完結した内容とし、追加の資料を参照する必要がないよう努めます。今後ともよろしくお願いいたします。
grep
grepコマンドの詳細解説
コア機能と目的 (Core Functionality and Purpose)
grepコマンドは、テキストの中から特定のパターン(文字列や正規表現)に一致する行を検索し表示するためのUnix系システムで非常に一般的なツールです。名前の由来は「Global Regular Expression Print(正規表現による全体検索と印刷)」にあります。主な用途は、ログファイルや設定ファイル、プログラムのソースコードなどから必要な情報を素早く抽出することです。grepは標準入力または指定したファイル群を行単位で読み込み、検索パターンにマッチした行のみを標準出力に書き出します(オプションによってはマッチしなかった行やファイル名のみ等を出力可能)。
開発や運用において、grepはフィルタとして頻繁に用いられます。他のコマンドの出力をパイプで繋いでgrepに渡し、必要な行だけを抜き出したり、大量のテキストから該当箇所を探し出すのに活躍します。シェルスクリプトから利用して、特定の文字列の有無で処理を分岐させる(終了ステータスを利用)といった使い方も広く行われています。
主なオプションと機能 (Options and Features)
grepには多数のオプションがあり、検索方法や出力形式を柔軟に制御できます。ここでは極端に特殊なものを除く主要なオプションと機能を説明します。各オプションの後にPOSIX標準外の機能には[Non-POSIX]、特定実装に依存する場合には[GNU](GNU版grep)、[BSD](BSD系grep。macOSのデフォルトgrepもこちら)、[BusyBox]などのタグを付けています。また、特定のシェルに固有の機能ではありませんのでシェル名タグは本項目では使用しません。
検索パターンの種類指定に関するオプション
-
-E,--extended-regexp(拡張正規表現の使用)【POSIX対応】
拡張正規表現 (ERE: Extended Regular Expression) でパターンを解釈します。デフォルトの**基本正規表現 (BRE)よりもリッチな表現が可能で、+,?,{m,n},|などのメタ文字をエスケープ無しで直接使用できます。例えば、grep -E "ab+cd|ef"は「abに続く1回以上のb、後にcd」または「ef」にマッチします。POSIX標準ではgrep -Eはegrep**コマンドと同等です(現代ではegrepは非推奨でgrep -Eに統合されています)。 -
-F,--fixed-strings(固定文字列として検索)【POSIX対応】
検索パターンを正規表現ではなく固定文字列として扱います。正規表現のメタ文字(.や*など)も通常の文字として扱われるため、特殊なエスケープ不要でリテラルな一致を検索できます。例えば、grep -F "a.c"は文字列「a.c」をそのまま探し、.を任意1文字とは解釈しません。grep -Fは**fgrep**コマンド(今は非推奨)と同等です。複数のパターンを同時に検索する場合、-Fは高速(Aho-Corasickアルゴリズム等の利用)で、大量のログから決まったキーワードを探す用途に適しています。 -
-G,--basic-regexp(基本正規表現の使用)【非標準オプション / 一部実装で利用可】
パターンを基本正規表現 (BRE) として解釈します。BREはPOSIXで定義されたデフォルトの正規表現で、+や?など一部メタ文字をそのまま使えずエスケープが必要になる点が拡張正規表現と異なります。通常、grepコマンドはオプション指定がなければBREとして動作するため、-Gオプションは明示的にBREモードにするためのものですが、ほとんどの場合デフォルト動作であるため指定は不要です(万一デフォルトが変更された特殊な環境やエイリアスを無効化したい場合に使用)。 -
-P,--perl-regexp(Perl互換正規表現の使用)【Non-POSIX】【GNUのみ】
パターンをPerl互換正規表現 (PCRE) として解釈します。PCREでは\d(数字)、\w(単語構成文字)、肯定/否定の先読みや後読みなど高度な正規表現機能が使用可能です。例えば、先読みを使ってgrep -P "foo(?=bar)" fileのように「後ろにbarが続くfoo」を検索できます【GNU】。注意: このオプションはGNU grepでのみサポートされ、BSD版grep(macOS標準のgrepなど)やBusyBox版grepにはありません。Perl互換の高度なパターンを使用したい場合、GNU grepがインストールされている環境で実行する必要があります。また、大文字小文字無視(-i)などとの組み合わせ時にはUnicode対応の挙動がPCREエンジン準拠になる点にも注意してください。 -
-e <パターン>,--regexp=<パターン>(検索パターンの指定)【POSIX対応】
検索したいパターン文字列を直接指定します。パターンがハイフン(-)で始まる場合や、複数のパターンを指定したい場合に使用します。grep -e "pat1" -e "pat2" fileのように複数回-eを使うと、いずれかのパターンにマッチする行を検索します(論理和OR)。-eオプションを使わずに最後の引数としてパターンを書くこともできますが、パターン文字列が-で始まるとオプションと誤認されるため、その場合は-eを使うか--(オプションの終わりを示す)を明示する必要があります。例:grep -e "-pattern" file(ハイフンから始まる"-pattern"を検索)。 -
-f <ファイル>,--file=<ファイル>(パターンファイルの読み込み)【POSIX対応】
指定したファイルから検索パターンを読み込みます。ファイルには改行区切りで複数のパターンを記述できます(空行は空パターン=どの行にもマッチするパターンとして扱われます)。複数の-fを指定して複数のファイルを読み込むことも可能で、-eで与えたパターンもすべて含め総和集合として扱われます(どれか1つでもマッチすれば行が選択される)。この機能は大量のキーワードリストで検索する場合や、長大な正規表現をスクリプト内に直接書く代わりにファイル管理したい場合に有用です。
《正規表現の基本 (BRE) と拡張 (ERE)の違い》:
上記の-E(拡張)未指定時は基本正規表現 (BRE)として扱われます。BREでは例えば「1回以上の繰り返し」はメタ文字+が使えず\+のようにバックスラッシュでエスケープして書く必要があります(もしくは繰り返し回数を指定できる波括弧量指定子を使い、¥{1,¥}のように書きます)。括弧によるグループ化も( はメタ文字でなく\(と書く必要があります。一方、-E指定のEREではこれらのエスケープが不要で、表現力も若干広がります(|によるオルタネーション(選択肢)のサポートなど)。なお、.(任意の1文字)や*(直前の0回以上の繰り返し)といった基本的なメタ文字はBRE/EREともに同様に使用できます。grepでは後方参照(以前の正規表現のグループにマッチした内容を再利用する機能)もサポートしており、BREでは\(…\)で囲んだグループは\1, \2...で参照できます(EREでも同様に動作します)。例えば、grep '\(foo[0-9]*\)\1' fileは「fooに数字が続いた文字列」が2回連続して繰り返される行を探します。このようにBREでも高度なパターンは可能ですが、複雑な表現は-Eを付けてEREを利用する方が見通しが良くなるでしょう。さらに高度な言語要素(肯定先読みのような機能)が必要な場合は前述の-P (PCRE)が必要になります。
マッチ条件変更に関するオプション
-
-i,--ignore-case(大文字・小文字を無視)【POSIX対応】
英字の大文字・小文字を区別せずにマッチさせます。例えば、grep -i "Error" fileは"Error","ERROR","error"など大小区別なく一致します。デフォルトではgrepは大文字小文字を区別するので、ケースインセンシティブな検索をしたい場合に-iを使用します。なおGNU grepでは対応する長オプション--no-ignore-caseもあり、これは環境変数や別オプションで-iが有効になっている場合に敢えて区別するよう指示するものですが、通常明示的に使う機会はあまりありません。 -
-v,--invert-match(マッチしない行の選択)【POSIX対応】
指定したパターンにマッチしなかった行を選択します。いわば出力を反転するオプションです。例えば、grep -v "^#"は「先頭が#ではない行」を全て表示します(設定ファイルなどでコメント行を除外したい場合に便利)。-vは-nや-lなど他のオプションとも組み合わせ可能です。ただし-v使用時の終了ステータスは通常通りで、マッチする行が一つもなかった場合に0(真)ではなく1(偽)となる点に注意してください(-vは出力を反転させますが、内部的には「マッチした行がなかった」=検索パターンに1つも一致しなかった場合と同義なので終了コード1になります)。 -
-w,--word-regexp(単語全体で一致)【Non-POSIX】【GNU/BSD対応】
パターンを単語単位で検索します。具体的には、マッチの前後が単語の境界である場合のみ一致とみなします(単語の境界とは、英数字やアンダースコア(**\w**相当)の文字と、それ以外の文字(空白や句読点、行頭行末など)の境目です)。例えば、grep -w "cat" fileは「cat」という独立した単語のみマッチし、「caterpillar」や「scatter」内の"cat"にはマッチしません。-wは複数単語をパターンに含めた場合それぞれについて単語境界をチェックします(正規表現で言うとそれぞれのパターンの前後に\b(単語境界)を暗黙的に付加したイメージです)。 -
-x,--line-regexp(行全体で一致)【POSIX対応】
行全体がパターンと完全一致する場合のみマッチと見なします。つまり、行頭から行末までがパターンにマッチする必要があります。例えば、grep -x "hello"とすると、その行に「hello」だけが書かれていれば一致しますが、「hello world」のように他の文字列を含む行は一致としません。複数のパターンを指定した場合(-eや-fで複数パターンがある場合)は、それらのいずれか一つが行全体に一致すればマッチ扱いになります。設定ファイルでキーと値が厳密に一致する行を探す場合など、余計な文字が混じっていないことを確認したい用途で便利です。
出力内容・形式に関するオプション
-
-n,--line-number(行番号の表示)【POSIX対応】
一致した行の行番号を先頭に表示します。ファイルごとに1から行番号が振られ、デフォルトでは<行番号>:<内容>の形式で出力されます。例えばgrep -n "main" program.cはprogram.c:10:int main(int argc, char *argv[]) {のようにファイル名と行番号を付与して表示します(ファイル名の表示有無は後述の-h/-Hやファイル数によります)。ログ解析時にどの箇所かを示したり、大きなファイル内でマッチ位置を特定したりする際に有用です。 -
-b,--byte-offset(バイトオフセットの表示)【Non-POSIX】【GNU/BSD対応】
一致した行のファイル先頭からのバイトオフセット(0バイト目を0とする通しのバイト位置)を表示します。出力形式は行頭にバイトオフセットが付加され、通常は<バイト位置>:<行内容>となります(-nと併用した場合は<バイト位置>:<行番号>:<行内容>)。例えばgrep -b "foo" fileとすると128:foo barのように、その行がファイルの128バイト目から始まることを示します。バイナリファイル中でパターンが出現する絶対位置を知りたい場合や、テキストであってもファイル内のデータ配置を解析するような高度なユースケースで使われます。 -
-H,--with-filename(常にファイル名を表示)【Non-POSIX】【GNU/BSD対応】
一致行を表示する際に必ずファイル名を先頭に付けて表示します。通常、grepは検索対象のファイルが1つだけの場合や標準入力からの入力の場合、行の前にファイル名を付けません(冗長さを避けるため)が、このオプションを付けるとファイル数に関係なくファイル名:行内容の形式で出力します。他の出力オプション(-n等)とも組み合わせて利用可能です。スクリプトで出力を処理する際に、常にファイル名が明示されている方が取り回しやすい場合などに役立ちます。 -
-h,--no-filename(ファイル名を表示しない)【Non-POSIX】【GNU/BSD対応】
複数ファイルを検索する場合でもファイル名を出力しないようにします。grepはデフォルトで検索対象が複数ファイルのとき各行に「ファイル名:」プレフィックスを付けますが、-hを付けるとこれを抑制できます。標準入力(パイプからの入力)では元々ファイル名は表示されませんが、例えばcat file1 file2 | grep patternのように複数ファイルを連結した入力でも、-hを指定すれば同様に純粋な行だけの出力となります。複数ファイル検索時にプレフィックスが不要なケース(結果をさらに別のコマンドにパイプするときなど)で有用です。 -
-c,--count(マッチした行数のみ表示)【POSIX対応】
検索結果の行数のみを表示します。一致した行の内容そのものは出力せず、各ファイルごとに何行マッチしたかを数えて数字を出力します。例えば、grep -c "ERROR" server.logが出力するのが5であれば、「server.log内で"ERROR"を含む行が5行あった」ことを意味します。複数ファイルを検索した場合、各ファイル名とそれぞれのカウントが別行で表示されます(例:file1:3とfile2:0のように)。注意:-cは各ファイル内のマッチ行数を数えます。同じ行に複数のマッチ箇所があっても1行にカウント1です。もし「出現回数」そのものを数えたい場合はgrep -oとの組み合わせ(後述)か別途工夫が必要です。また、他の出力系オプション(-n,-l等)とは排他的な扱いとなり、同時指定した場合GNU grepでは最後に指定したものが有効になる点にも注意してください(POSIXでは-c,-l,-qを同時指定した場合の動作は未定義なので、同時に使わない方が無難です)。 -
-l,--files-with-matches(マッチしたファイル名のみ表示)【POSIX対応】
指定したパターンに1行以上マッチしたファイルのファイル名だけを表示します。各ファイルについて最初のマッチが見つかった時点でそのファイルの検索を打ち切り、次のファイルに進むため効率的です(大量のファイルを調べる際に有用)。出力は各ファイル名が改行区切りで並ぶ形になり、標準入力から検索した場合は"(standard input)"(GNU/BSDの場合)と表示されます。一方、マッチが一件もなかった場合は何も出力しません。このオプションは、例えば「ある文字列を含むファイルはどれか」を調べたい場合や、findコマンドと組み合わせて条件に合うファイル群を素早く得たい場合などに使われます。 -
-L,--files-without-match(マッチのないファイル名のみ表示)【POSIX対応】
-lとは逆に、全くマッチが無かったファイルの名前だけを表示します。例えばgrep -L "TODO" *.txtとすると、*.txtで指定したテキストファイルのうち「TODO」という文字列が一度も出現しないファイル名がリストされます。オプションの性質上、こちらも各ファイルで途中マッチが見つかった時点でそのファイルは出力対象から外し、すぐ次のファイルに進みます。多数のファイルの中から、特定の語が含まれていないファイルを探すといった用途に利用できます。 -
-o,--only-matching(マッチ部分のみ表示)【Non-POSIX】【GNU対応(一部BSDもサポート)】
行全体ではなく、パターンにマッチした部分文字列のみを出力します。デフォルトではgrepは該当する行全体を表示しますが、-oを付けると各マッチ箇所だけを表示します(1行中に複数マッチがあればそれぞれ個別に改行区切りで出力)。例えば、grep -o -E "[0-9]{3}-[0-9]{4}" addresses.txtとすると、電話番号のようなパターンにマッチした部分(例: 123-4567)だけが1行につき1箇所ずつ抽出されます。文字列抽出やカウントに利用する場合に便利ですが、このオプションを使うと元の行単位の情報(他のカラムなど)が失われる点には注意が必要です。 -
-q,--quiet,--silent(クワイエットモード)【POSIX対応】
一切出力を行わないモードです。マッチの有無(終了ステータス)だけを利用したい場合に使います。他の出力系オプションとの併用を無効化し、最初のマッチが見つかった時点で即座に終了します(高速化にも繋がります)。シェルスクリプト内で「パターンが見つかったかどうか」の判定に用いる場合、grep -q "string" fileのようにしておき、直後にbash if [ $? -eq 0 ]; then ...と終了コードを調べる、あるいはbash if grep -q "string" file; then ... fiのように条件判定に直接利用します。成功時(1行以上マッチ)に0、失敗時(マッチ0件)に1を返すのは通常のgrepと同様です。注意:-q使用時もエラー(指定ファイルが存在しない等)の場合は終了ステータス2となることがあるので、完全に無視するには2>/dev/null等でエラー出力も処理してください。また-qは-cや-lと同様に排他的扱いのため、同時に指定しないようにします。 -
-s,--no-messages(エラーメッセージ非表示)【POSIX対応】
ファイル読み込みエラーのメッセージを抑制します。存在しないファイルや権限のないファイルを指定した際に通常表示されるエラー(例:grep: filename: No such file or directory)を表示しなくします。スクリプトで意図的に存在しない可能性のあるファイルを検索対象に含めてもエラーを煩雑に出力させたくない場合などに有用です。ただし、このオプションはエラーそのものを無視するわけではない点に注意してください(終了ステータスは通常通りエラーを反映しますし、マッチ結果以外のエラー以外のメッセージ、例えば正規表現の構文エラーなどは抑制されません)。
コンテキスト表示に関するオプション
-
-A <行数>,--after-context=<行数>(後のコンテキスト行表示)【Non-POSIX】【GNU/BSD対応】
各マッチ行について、その後に続く指定行数分の行も一緒に表示します。例えば、grep -A2 "ERROR" app.logとすると、"ERROR"を含む行に加えてその行の直後2行も合わせて出力します。複数のマッチが離れた場所で発生した場合、異なるマッチグループの間は1行の区切り線(--とだけ書かれた行)が挿入され、どこまでが一つのマッチに対するコンテキストか判るようになっています。このオプションはログ中のエラー前後の状況を確認したり、設定ファイル中のキーワード周辺の内容を合わせて把握したい場合に便利です。 -
-B <行数>,--before-context=<行数>(前のコンテキスト行表示)【Non-POSIX】【GNU/BSD対応】
マッチ行の直前指定行数分の行も表示します。使い方や動作は-Aとほぼ同様で、-B3なら各マッチの前3行も含めて出力します。-Aと-Bを組み合わせて併用することも可能で、その場合前後両側のコンテキスト行が表示されます。例えばgrep -B1 -A1 "important" fileはマッチ行の前後1行ずつを追加表示します。 -
-C <行数>,--context=<行数>(前後のコンテキスト行表示)【Non-POSIX】【GNU/BSD対応】
マッチ行の前後合計<行数>行(前後それぞれ<行数>行ずつ)を表示します。-C2であれば-B2 -A2と等価です。単に-C(行数省略)とするとデフォルトで2行のコンテキストを表示します。また、ショートハンドとして**-<数字>**(ハイフンに続けて数字だけ)でも同じ効果があり、例えばgrep -4 "pattern" fileは-C4と同じ意味になります【GNU】。これらコンテキスト系オプションは便利ですが、出力されたテキストにはマッチした部分への強調表示はされないため(後述の--colorを使えば強調可能)、どの行がマッチしたか注意して読む必要があります。また、大きすぎるコンテキストを指定すると出力量が膨大になるので注意してください。 -
--group-separator=<文字列>(コンテキストグループの区切り指定)【Non-POSIX】【GNU対応】
コンテキスト表示を行う際に、デフォルトではコンテキストブロック間に--という区切り行が挿入されますが、このオプションで別の区切り文字列を指定できます。例えば--group-separator="===="とすれば区切り行が====になります。--no-group-separatorと指定すれば区切り行を挿入しないことも可能です。一般にはデフォルトのままで十分なため、これらは高度なレイアウト調整が必要な場合に使います。 -
-T,--initial-tab(タブ位置調整)【Non-POSIX】【GNU対応】
行番号(-n)やファイル名プレフィックス(複数ファイル検索時)を付けた際に、内容部分の先頭をタブで揃えるオプションです。これにより、複数ファイルのgrep結果をカラム状に見やすく整列できます。具体的には、例えば行番号の桁数が異なる場合でもタブにより行内容の開始位置を統一できます。見た目上の調整なので機能上必須ではありませんが、レポートに結果を貼り付ける際など整形したい場合に役立つことがあります。
再帰検索・ファイル指定に関するオプション
-
-r,--recursive(ディレクトリを再帰的に検索)【Non-POSIX】【GNU/一部BSD対応】
ディレクトリを指定した場合に、そのディレクトリ配下の全てのファイルを再帰的に検索します。例えばgrep -r "TODO" src/とすると、srcディレクトリ以下の全ファイルを巡回して「TODO」を含む行を探します。-rを付けない通常のgrepはディレクトリを引数に取るとエラーとなるか(GNU grepではエラー:grep: ディレクトリ名: Is a directory)、BSD grepではディレクトリをファイルとみなして読み込もうとして失敗します。-rを指定することでディレクトリを検出すると自動的にその中のファイル群を探索対象に含めます。
注意:-rオプションはシンボリックリンクの扱いに注意が必要です。GNU grepでは-rではシンボリックリンクを辿らず、リンク自体をファイルとして扱います(リンク先がディレクトリでも再帰しません)。無制限にリンクもたどって再帰したい場合は-Rを使用してください。 -
-R,--dereference-recursive(シンボリックリンクを辿り再帰検索)【Non-POSIX】【GNU/一部BSD対応】
-rと似ていますが、シンボリックリンクを辿って再帰検索します。-Rは--recursiveにリンク追跡を加えた挙動で、例えばディレクトリへのシンボリックリンクがあればその先のディレクトリ内容も含めて検索します。循環参照(シンボリックリンクが互いに指し合って無限ループするようなケース)には注意が必要ですが、通常はファイルシステム内を漏れなく検索したい場合に-Rを使います。 -
-d <動作>,--directories=<動作>(ディレクトリ指定時の動作)【Non-POSIX】【GNU/BSD対応】
ディレクトリを引数に取った際の動作を細かく制御します。<動作>として指定できるのは、read(通常ファイルのように読み込む)、recurse(再帰的に辿る)、skip(無視してエラーも出さない)の3種です。デフォルトではgrepは-r未指定時にディレクトリを引数に与えるとエラーを表示します(GNU grepではエラー表示+終了コード2、BSD grepではread扱いで中身を読もうとするが通常失敗)。-d skipを指定すると、明示的にディレクトリを無視してエラー表示もしないようになります。一方、grep -d recurse "pattern" dir/とすれば-rと同様に再帰検索をします(GNU grepでは-rと-d recurseは同義です)。シェルでファイル名パターンを展開した際に意図せずディレクトリが含まれる可能性がある場合など、このオプションで処理を制御できます。 -
--include=<グロブ>(再帰検索対象に含めるファイル指定)【Non-POSIX】【GNU/BSD対応】
再帰検索時に、検索するファイル名をグロブパターンでフィルタできます。例えば、grep -R --include="*.log" "ERROR" /var/logsとすると.logで終わるファイル(ログファイル)に限って「ERROR」を探します。複数の--includeパターンを指定可能で、その場合後に指定したパターンほど優先(同一ファイル名が複数パターンにマッチした場合、最後のルールが適用)されます。--includeが一つも無ければ全てのファイルが対象になります。このオプションを使うことで不要なファイルを除外し検索効率を上げたり、特定のファイル種類だけに絞った検索が可能になります。 -
--exclude=<グロブ>(再帰検索対象から除外するファイル指定)【Non-POSIX】【GNU/BSD対応】
再帰検索時に、検索から除外したいファイル名パターンを指定します。--exclude="*.bak"のように使うとバックアップファイルを無視できます。--includeとの組み合わせでは与えた順番に評価され、どちらも複数指定できます(最終的にincludeにもexcludeにもマッチしないファイルが検索対象)。なお、--exclude/--includeで指定するパターンはディレクトリを含むフルパスに対してマッチします。例えば--exclude="*/test/*"のように書けばパスに/test/を含むすべてのファイルをスキップすることになります。 -
--exclude-dir=<グロブ>,--include-dir=<グロブ>(再帰検索時のディレクトリ除外/限定)【Non-POSIX】【GNU/BSD対応】
こちらはディレクトリ名に対するinclude/excludeフィルタです(-R/-r指定時のみ有効)。--exclude-dir=".git"とすればGitリポジトリディレクトリを再帰検索から省きます。--include-dirを使えば特定のディレクトリのみ再帰検索対象にすることも可能です。これも複数指定や順序規則は--include/--excludeと同様です。バージョン管理のディレクトリや一時ディレクトリを除外した検索(例えばソースコード検索からnode_modulesや.gitを除外)などに役立ちます。
バイナリファイル・特殊ファイルに関するオプション
-
-a,--text(バイナリをテキストとして扱う)【Non-POSIX】【GNU/BSD対応】
通常、grepはバイナリファイル(NULLバイトを含むような非テキストファイル)に出会うと、効率のためそこで検索を打ち切り**「Binary file <ファイル> matches」というメッセージを出力します(何かしらパターンに当たった場合)。-aを指定すると、たとえバイナリ的な内容でもテキストファイルとして読み続ける**ようになります。例えば、grep -a "PNG" image.pngのようにすると画像ファイル内もテキストとして読もうとし、バイナリの中に「PNG」という文字列があれば(たまたまですが)表示します。バイナリファイル内のテキストリソースを検索したい特殊な状況で使われます。 -
-I(大文字i)【Non-POSIX】【GNU/BSD対応】
-aとは逆で、バイナリファイルは常に「一致しなかった」ものと見なすオプションです。GNU grepではこれは--binary-files=without-matchに相当します(-I単独でOK)。このオプションを付けると、たとえバイナリ内に検索文字列が含まれていても一切報告されず無視します。再帰検索で画像や実行ファイルが大量にあるディレクトリを走査する際など、いちいち「バイナリファイルです」とメッセージが出るのを抑止しつつ無視したい場合に有用です。 -
--binary-files=<値>(バイナリファイルの扱い指定)【Non-POSIX】【GNU対応】
上記-aや-Iの長オプション版かつ詳細版です。<値>にはbinary(デフォルト)、text(-a相当)、without-match(-I相当)が指定できます。オプション組み合わせではなく明示的に振る舞いを決めたいときに使います。例えば、grep --binary-files=text -R "foo" .のようにすれば再帰検索中にバイナリもテキスト扱いします。 -
-D <動作>,--devices=<動作>(デバイスファイル等の扱い)【Non-POSIX】【GNU対応】
デバイスファイルやFIFO(名前付きパイプ)、ソケットといった特殊ファイルを検索対象に含めた場合の挙動を制御します。readかskipを指定でき、デフォルトはreadです。これにより、例えばgrep -R -D skip "foo" /procのようにすれば、Linuxの/procファイルシステム内の特殊ファイルを読み飛ばしてエラーを避けることが可能です。通常は意識する必要はありませんが、特殊ファイルが紛れ込む可能性があるパスを再帰検索する際に使用します。
カラーハイライト・その他の便利なオプション
-
--color[=<when>],--colour[=<when>](マッチ部分のカラー表示)【Non-POSIX】【GNU対応】
出力結果において、マッチ部分を色付きでハイライト表示します。<when>にはauto(デフォルト、出力先が端末のときのみ色付け),always(常に色付け),never(色付けしない)を指定できます(例:--color=always)。色設定は環境変数GREP_COLORS(または古いGREP_COLOR)でカスタマイズ可能で、デフォルトではマッチ部分が赤字で強調表示されます。例えば、ターミナル上でgrep --color=auto "main" *.cとすると、見つかったmainの文字列部分が赤く表示され、一目でどこにマッチがあるか分かりやすくなります。注意:--colorオプションはGNU grep特有の機能です。macOS標準のBSD grepには存在しないため、macOSで色付き出力を使いたい場合はGNU grepをインストールする(grep --versionで"GNU"の文字を確認)必要があります。 -
--line-buffered(行単位で出力をフラッシュ)【Non-POSIX】【GNU対応】
grepの出力を行ごとにバッファをフラッシュするモードです。通常GNU grepはブロックバッファリングを行うため、パイプ経由で他のコマンドに繋いだ場合や、ファイルへリダイレクトする場合には、ある程度出力が溜まるまで書き出しを保留します(パフォーマンス向上のため)。--line-bufferedを付けると、マッチした行を検出するごとに即座に出力しフラッシュします。リアルタイムでログを監視するような用途(tail -f | grep ...など)で、パイプ先でも1行ずつ処理したい場合に役立ちます。ただし、このモードではパフォーマンスが若干低下する可能性があるため必要な場合のみ使用します。
以上が主なオプションと機能です。次に、これらのオプションを実際に使った例を示し、それぞれの出力結果や挙動を確認していきます。
使用例 (Examples)
ここでは、基本的なものから高度なものまで、上記で説明した各種オプションの使い方を具体的な使用例とともに示します。例では、サンプルの入力ファイルやコマンドの出力を説明のために記載しています。$はシェルのプロンプト、#はコメントを示します。
1. 基本的な文字列検索
最もシンプルな使い方として、ファイル内から直接文字列を検索する例です。
# file.txt 内に "hello" を含む行を検索する
$ grep "hello" file.txt
hello world # file.txt中の "hello" を含む行が表示される
上記の例ではfile.txtの中から「hello」を含む行がそのまま表示されます。指定したパターン"hello"は部分一致でもマッチするため、「hello」が行の一部に出現すればその行全体が出力されます。検索パターンには基本正規表現が適用されますが、この例では特別な正規表現メタ文字は含んでいない単純な文字列なので、そのままリテラル検索となります。
複数ファイルを指定することもでき、また-をファイル名に指定すると標準入力から読み取ります。
# file1.txt と file2.txt の両方から "error" を含む行を検索
$ grep "error" file1.txt file2.txt
file1.txt:Disk error occurred # 出力にはファイル名が付加される(複数ファイル指定時)
file2.txt:Read error: Resource not found
# パイプで他コマンドの出力をgrepに渡す(標準入力として検索)
$ dmesg | grep "memory"
[ 0.000000] Memory: 16384K/32768K available (1024 kernel code, ...)
上記では複数ファイル指定により、出力結果の行頭にfile1.txt:のようにファイル名が付与されています。dmesg | grep "memory"の例では、カーネルログ表示コマンドdmesgの出力から「Memory」を含む行だけが抽出されています。このようにgrepはパイプと組み合わせて他コマンドのフィルタとしてしばしば使われます。
2. 大文字小文字の無視と否定マッチ
次に、検索時に大文字・小文字を区別しない-iオプションと、一致しない行を選択する-vオプションの例です。
# "Warning" という単語を case-insensitive(大小無視)で検索
$ grep -i "warning" messages.log
WARNING: Low battery # "WARNING" もヒットする
Warning: Network disconnected # "Warning"(先頭大文字)もヒットする
# ファイル config.cfg 内で "#"から始まる行(コメント行)以外を表示
$ grep -v "^#" config.cfg
hostname=server1 # "#"で始まらない設定行が表示される
port=8080
最初の例では、ログファイルmessages.logから"warning"という語にマッチする行を-iで大文字小文字を無視して検索しています。その結果、"WARNING"(全大文字)や"Warning"(頭字のみ大文字)といったバリエーションもマッチし、表示されていることが確認できます。
2つ目の例では、設定ファイルconfig.cfgからコメント行を除外して内容を表示しています。正規表現"^#"は「行頭に#」を意味し、-vオプションにより「このパターンにマッチしない行」を出力しています。結果として、#で始まる行(コメント行)は外され、それ以外の行のみが表示されます。このようにgrep -vは、フィルタリングして不要な行を取り除く際に重宝します。
3. 正規表現によるパターン検索(基本と拡張)
grepは正規表現を使った強力なパターンマッチが可能です。ここでは**基本正規表現 (BRE)と拡張正規表現 (ERE)**の違いが分かる例を示します。
まず、**基本正規表現(BRE)**での例です。
# プラス記号を含む行を検索(BREモード、デフォルト)
$ echo "a+b" | grep "a+b"
a+b # 「a+b」という文字列自体にマッチする行が出力される
# 「a」の一回以上の繰り返しに続く「b」にマッチ(BREでは「\+」を使用)
$ echo "aaab" | grep "a\+b"
aaab # "a+" にマッチ(3個の "a" に対し \+ が効いている)
1つ目では、エコーで出力した文字列"a+b"に対し、grepでパターン"a+b"を検索しています。BREでは+はメタ文字ではないため、リテラルのプラス記号として解釈されます。この結果、入力文字列a+bそのものに一致し、行が出力されています。
2つ目では、パターン"a\+b"を使っています。BREでは"\+"とエスケープすることで「直前の文字aが1回以上繰り返す」という意味になり、"aaab"の行にマッチしています。逆に言えば、エスケープしない"a+b"は上記のようにリテラルの+として扱われ、"aaab"にはマッチしません。
次に、**拡張正規表現(ERE)**で同様の検索を行う例です。
# EREモードで「a」の一回以上の繰り返しに続く「b」にマッチ
$ echo "aaab" | grep -E "a+b"
aaab # EREでは + がメタ文字として機能し、マッチする
# EREモードでオルタネーション(選択肢)を使用した例
$ echo -e "foo\nbar\nbaz" | grep -E "foo|baz"
foo # "foo" にマッチした行
baz # "baz" にマッチした行("bar" はどちらにも当てはまらず除外)
grep -E "a+b"では、EREモードのため+がメタ文字となり、「aが1回以上、その後b」というパターンになります。これに"aaab"がマッチし行が出力されます。BREの例と見比べると、同じ正規表現的な意味合いでもEREではエスケープが不要で簡潔に書けることが分かります。
また、2つ目のEREの例では、"foo|baz"というオルタネーションを使用しています。これは「fooまたはbazにマッチする」正規表現で、エコーで与えた3行の入力のうち「fooを含む行」「bazを含む行」が選択的に出力されています。BREでもオルタネーションを実現するにはエスケープして\|と書く必要がありますが、EREではそのまま|を使えるため複数パターンを表現しやすくなっています。
4. 複数パターンの同時検索(-e, -fオプション)
grepで複数のパターンを一度に検索する方法として、-eオプションを複数使う方法と、-fオプションでパターンファイルを指定する方法があります。
# 複数の文字列パターンを -e で指定して検索(OR条件)
$ grep -n -e "ERROR" -e "WARN" app.log
5:ERROR: Disk failure # "ERROR" にマッチ(行番号付き)
17:WARN: Low memory # "WARN" にマッチ(行番号付き)
この例ではapp.logに対して"ERROR"と"WARN"の2つのパターンを-eで指定し、-nも付けて行番号付きで検索しています。結果として、5行目に"ERROR"を含む行、17行目に"WARN"を含む行がマッチして表示されています。どちらかのパターンにマッチすれば出力されるため、論理和(OR)検索となります。grep -E "ERROR|WARN"と正規表現で書いても同様の結果が得られますが、-eを使うとシェルのクォートを分けて書けるので長いパターンや複数単語でも見やすく記述できます。
次に、パターンファイルを使用する例です。予め検索したいキーワードをファイルに列挙しておき、それを-fで読み込ませます。
# patterns.txt に複数のパターンが記載されているとする
$ cat patterns.txt
apple
banana
cherry
# patterns.txt内のパターンのいずれかにマッチする行をdata.txtから検索
$ grep -H -f patterns.txt data.txt
data.txt:apple pie is delicious
data.txt:I like banana smoothies
上記では、patterns.txtに3つの単語が書かれています。このファイルをgrep -f patterns.txt data.txtで指定することで、data.txt内に**「apple」か「banana」か「cherry」**のいずれかが含まれる行を探しています。-Hオプションでファイル名を強制表示しているため、結果はdata.txt:<行内容>の形式になっています。実行結果ではappleを含む行とbananaを含む行が表示され、cherryに該当する行は無かった(あるいは表示されなかった)ことが分かります。パターンが非常に多い場合、-eを多数書くよりもファイルにまとめて-fで渡す方が管理しやすいでしょう。
5. 出力内容の制御(行番号、ファイル名抑制、マッチ部分抽出など)
grepの各種出力制御オプションの実例を示します。
# 複数ファイルを検索し、行番号も表示
$ grep -n "main" src/file1.c src/file2.c
src/file1.c:10:int main(int argc, char *argv[]) {
src/file2.c:7: // calling main function
# 複数ファイル検索だがファイル名は表示しない(-h)、マッチ部分のみ表示(-o)
$ grep -h -o -E "https?://[A-Za-z0-9./-]+" docs/page*.html
http://example.com/page1 # マッチしたURL部分のみ表示。ファイル名は出さない
https://example.com/test # docs/page2.html 内の一致もファイル名なしで表示
最初のコマンドでは、src/file1.cとsrc/file2.cという2つのファイルに対し、"main"という文字列を検索しています。-nオプションを付けているため、該当行の行番号が表示され、さらに複数ファイルを指定したためファイル名も出力されています。src/file1.c:10:という出力は「file1.cの10行目で一致」と読み取れます。
2つ目のコマンドは、HTMLファイル群(docs/page1.html, docs/page2.html, ...)からURLを抽出する例です。正規表現"https?://[A-Za-z0-9./-]+"は「http://またはhttps://に続く、英数字・ピリオド・スラッシュ・ハイフンからなる文字列」を意味し、おおよそURLのパターンにマッチします。-oオプションを付けているため、マッチした部分(URL文字列)だけが表示され、HTMLファイルの他の部分は表示されません。また-hオプションでファイル名の出力を抑制しているため、複数ファイルにまたがる結果ですがファイル名のプレフィックス無しで一覧できます。このように-oは特定のデータ(URLやメールアドレス等)を抽出する用途に便利ですが、抽出結果だけでは元の文脈が分からないこともあるので、必要に応じて-nで元の行番号を付加するなど工夫すると良いでしょう。
6. マッチ件数・ファイル名のみの表示(-c, -l, -L)
-c(件数カウント)および-l/-L(ファイル名のみ)の使用例です。
# エラーログ内で "FAIL" を含む行が何行あるか数える
$ grep -c "FAIL" error.log
3
# => 3行マッチした(内容は表示しない)
# 複数ファイルの中から "TODO" を含むファイル名だけを列挙
$ grep -l "TODO" src/*.py
src/util.py
src/db.py
# 複数ファイルの中から "DONE" が全く含まれないファイル名を列挙
$ grep -L "DONE" testcases/*.txt
testcases/case3.txt
grep -c "FAIL" error.logは、error.log内に「FAIL」という文字列が出現する行数をカウントし、その数のみを出力します。上記結果では3と表示され、マッチ内容は表示されません。複数ファイルに対して-cを使うと、それぞれのファイル名とカウントが出ます。例えば2ファイルに適用してfile1:3とfile2:0のような結果になることがあります(0も明示的に出力される点に留意)。
次のgrep -l "TODO" src/*.pyでは、srcディレクトリ内のPythonファイル(*.py)を対象に、「TODO」という文字列を含むファイル名のみをリストアップしています。出力結果から、util.pyとdb.pyの2つに"TODO"コメントが存在することがわかります。これらファイルはマッチが見つかった時点で検出されているため、例えばutil.py内に複数の"TODO"があっても結果への表示は1度きりです。
最後のgrep -L "DONE" testcases/*.txtは、testcasesディレクトリ内のテキストファイル群から「DONE」が一度も出現しないファイルを列挙しています。結果にはcase3.txtのみが表示されており、これは裏を返せばcase3.txt以外の全てのファイルには"DONE"が少なくとも一箇所含まれていることを意味します。-Lは**「含まないファイルを探す」**ニーズに応えるもので、例えば「この文字列が未翻訳のまま残っているファイルを探す」等に応用できます。
7. 再帰的な検索とファイルフィルタ(-r, --include, --exclude)
ディレクトリ全体を検索する場合の例です。-rや-Rといった再帰オプションに加え、--includeで特定のファイルタイプだけに絞り込む方法、--excludeで除外する方法を示します。
# カレントディレクトリ以下の全ての*.cファイルから"malloc"を含む行を探す
$ grep -R --include="*.c" "malloc" .
./src/main.c:45: buffer = malloc(size);
./src/utils/mem.c:88: malloc_stats();
# (*.c以外のファイルは検索されない)
# ディレクトリ配下を再帰検索するが、バイナリや.gitディレクトリは除外する
$ grep -R -I --exclude-dir=".git" "TODO" .
src/util.c:/* TODO: optimize this loop */
docs/intro.txt:TODO - add more examples
# (-Iによりバイナリファイルは無視、--exclude-dirで.git以下は無視)
1つ目の例では、カレントディレクトリ.以下を-Rで再帰的に探索しつつ、--include="*.c"によりC言語ソースファイル(拡張子.c)に限定して"malloc"を検索しています。結果にはパス付きでmain.cとmem.cの該当行が表示されました。ここで--includeを使わず単にgrep -R "malloc" .とすると、.c以外のファイル(例えばバイナリや他のテキストファイル)も含め全て検索対象となります。ファイル数が多い場合--includeや--excludeによる対象絞り込みはパフォーマンス改善にも繋がりますし、不要なヒットを避けることで結果の精度も上がります。
2つ目の例は、コードリポジトリ全体から"TODO"を探すケースを想定しています。-Rで再帰検索しつつ、-Iでバイナリファイルは無視、さらに--exclude-dir=".git"でGitのバージョン管理ディレクトリを除外しています。出力結果にはsrc/util.cとdocs/intro.txtという2つのファイルが表示され、それぞれの中身に"TODO"が見つかったことがわかります。.gitディレクトリ以下には大量のオブジェクトファイル等があるため、通常検索しても有意義ではなく、--exclude-dirで除くのがベストプラクティスです。同様にnode_modulesやvendorディレクトリ等、あなたのプロジェクトで検索不要なパスがあれば除外指定すると良いでしょう。
8. コンテキスト付き検索(-A, -B, -C)
マッチした行の前後も含めて表示するコンテキストオプションの例です。
# server.logから"ERROR"を含む行と、その直前2行・直後1行を表示
$ grep -B2 -A1 "ERROR" server.log
Jul 12 10:15:20 Starting backup process
Jul 12 10:15:21 Connecting to server
Jul 12 10:15:22 ERROR: Connection timeout
Jul 12 10:15:23 Retrying connection
--
Jul 13 08:00:00 Daily summary start
Jul 13 08:00:01 ERROR: Summary generation failed
Jul 13 08:00:02 Please check the input data
server.log内から"ERROR"行を探し、その前2行と後1行を表示する設定(-B2 -A1)にしています。出力結果を見ると、まず7月12日のログでエラーが発生した部分について、エラー行の直前にある「バックアッププロセス開始」と「サーバー接続中」の2行、エラー行「Connection timeout」、その直後の「リトライ中」の行がまとまって表示されています。そのブロックと次のブロックの間に--という区切り行があり、次に7月13日のエラー箇所に関するログ行が同様に表示されています。7月13日のブロックではエラー行の前に表示すべき行が1行もなかった(もしくは別のマッチで独立している)ため、エラー行と後1行のみ表示されています。
このように、-Bや-Aを使うとマッチ箇所の周辺情報を把握できるため、ログ解析などで前後関係を確認するのに役立ちます。一方で、複数のマッチブロックがある場合には上記のように--で区切られるため、この区切り自体が不要な場合は--no-group-separatorオプションを利用できます。なお、コンテキスト行にも行番号(-n)やファイル名(-H/-h)は付加されますので、見失わないように適宜活用してください。
9. Perl互換正規表現による高度なマッチ(-Pオプション) [高度な例]
最後に、GNU grep限定の-Pオプションを使った高度な正規表現の例です。ここでは先読みというテクニックを用いて、ある文字列の後に別の文字列が続くケースをマッチさせます。
# Perl正規表現を用い、"foo"の後ろに"bar"が続く場合のみ"foo"をマッチさせる
$ echo -e "foobar\nfooqux" | grep -P -o "foo(?=bar)"
foo
# ("foobar"の中の "foo" は表示されたが、"fooqux" の "foo" は無視された)
# Perl正規表現で単語境界記号 \b を使用(例: 数字のみからなる単語を抽出)
$ echo "Order 12345 shipped on 2023-07-15." | grep -P -o "\b\d+\b"
12345
1つ目の例では、入力として"foobar"と"fooqux"の2行を用意しています。正規表現パターン"foo(?=bar)"は「fooの後ろにbarが続く場合のfoo」という肯定先読みを使ったパターンです。-Pを付けているためgrepはPCREモードで実行され、この高度なパターンを解釈できます。さらに-oも指定しているので、マッチ部分そのものだけを出力します。結果を見ると、"foobar"の中のfooは条件を満たすため出力されていますが、"fooqux"のfooは後ろにbarが無いため無視されています。このように-Pを使うと文字列の文脈を指定したマッチなど、通常のgrepでは困難な検索が可能になります。
2つ目の例では、"\b\d+\b"というPCREならではの正規表現を使用しています。\dは数字([0-9]と同等)、\bは単語境界(英数字と非英数字の境目)を表すエスケープシーケンスです。このパターンは「周囲が単語境界で囲まれた1個以上の数字」、すなわち「他の文字とくっついていない独立した数字列」を意味します。入力文字列"Order 12345 shipped on 2023-07-15."には独立した数字列として12345(前後に空白と文字があり単独の単語扱い)があり、2023-07-15はハイフンで繋がっているため単語境界では区切られず一つの長い単語とみなされます。そのため、出力結果には12345のみが表示されています。この例は、PCREを使うとPOSIXでは使えない\dや\bが利用できること、そして\bにより単語単位の抽出ができることを示しています(POSIX grepでも-wである程度代用可能ですが、\bほど柔軟ではありません)。
注記: -Pは強力ですが、使用できる環境(GNU grep限定)とパフォーマンスに注意が必要です。複雑なPCREパターンは動作が重くなることがあるため、必要な場合のみ使い、不要なら-Eや-Fで済ませるのがベターです。また、macOS等でGNU grepを使う際はgrep -Pがエラーなく動作するか事前に確認してください(BSD grepには-Pが無く、GNU grepもバージョンによってはPCREライブラリ組み込みがオプションの場合があります)。
一般的なユースケースとベストプラクティス (Common Use Cases and Best Practices)
grepの典型的な活用シーンと、その際のベストプラクティスをまとめます。
-
ログファイルやテキストデータからのフィルタリング: システムログ、アプリケーションログ、サーバのアクセスログ等から特定のキーワード(エラーや警告、特定ユーザIDなど)を検索するのはgrepの代表的用途です。例えば、
grep -i "error" /var/log/syslogのようにすれば最新のシステムログからエラー関連の行だけを抜き出せます。ログ監視ではtail -fと組み合わせてリアルタイムに新規エラーを監視することもあり、その場合tail -f logfile | grep "ERROR"とするか、前述の--line-bufferedを用いると確実です。 -
設定ファイルやコードからの該当行抽出: 複数の設定ファイルの中から特定の設定項目を探す(例:
/etc配下からListenという行を探す)、大量のソースコードから関数やクラスの定義箇所を探す(grep -R "class MyClass" .など)は開発時によくあるシナリオです。最近ではackやag (The Silver Searcher),rg (ripgrep)など専用のコード検索ツールもありますが、環境になくてもgrepで十分実現できます。コード検索には--includeオプションでファイルタイプを絞ったり、明確な単語として探す場合は-wを使うとヒット精度が上がります。 -
シェルスクリプトでの条件分岐: grepの終了ステータス(0: マッチあり, 1: マッチなし, 2: エラー)を利用し、シェルスクリプト内で文字列の存在チェックを行うのは定番です。例えば、設定ファイルに
ENABLE_FEATURE=yesという行があるか判定して処理を変える場合に、bash if grep -q "^ENABLE_FEATURE=yes" config.txt; then ... fiのように書きます。-qを付けることで出力が抑制されスクリプトの標準出力を汚さない利点があります。また、特定コマンドの出力に文字列が含まれるかで分岐する場合にも、bash if some_command | grep -q "pattern"; then ... fiのようにパイプでgrepを挟むことで実現できます。 -
効率的な検索(固定文字列と並列処理): 検索したい内容が決まった固定文字列であり、正規表現のパワーが不要な場合は**
grep -Fを使うことでパフォーマンスを向上できます。-Fは正規表現エンジンを使わず単純な部分文字列検索を行うため、大量のデータをさばく際に高速に動作します。また、複数のパターンを同時に検索したい場合も、一つ一つ別のgrepでパイプを繋ぐ(論理AND的な絞り込みには有効ですがORには冗長)のではなく、-E 'pat1|pat2'や-e pat1 -e pat2で1回のgrepでまとめて検索**する方が効率的です。GNU grepは内部でマルチスレッドを使った並列検索機能はありませんが、例えばxargs -P(並列)オプションと組み合わせて複数ファイルを並行処理する手法もあります。ただし一般にはIOがボトルネックになるため、よほど巨大なデータでなければシングルプロセスのgrepでも問題ないでしょう。 -
出力結果の見やすさ: 人が読む目的でgrepを使う場合、
--color=autoを有効にしておくことを推奨します。これは環境によってはデフォルトで有効になっていることもありますが、そうでない場合はシェルの設定ファイルでalias grep='grep --color=auto'のように設定すると便利です。ハイライトによってヒット箇所が視覚的に捉えやすくなります。スクリプトなどで色コードが不要な場合は--color=neverで無効化もできます。 -
ポータビリティと互換性:
grepを使ったスクリプトを他のシステムでも動かす可能性があるなら、できるだけPOSIX標準のオプションに留めることが重要です。例えば-Pや--colorは非標準のため、環境にGNU grepが無いと使えません。同様に-oや-rも歴史的にはGNU拡張なので、古いUnixや組込みBusyBox環境では動かない可能性があります。代替手段として、grep -oの代わりにgrep結果をパイプでsedやawkに渡して抽出する、grep -rの代わりにfind -exec grepを使う等があります。最近の主要OS(Linux, macOS, *BSDなど)ではそれなりにこれら拡張も使えますが、**[Non-POSIX]**と記した機能は互換性に注意して使用してください。 -
正規表現の利用とエスケープ: grepの正規表現は強力ですが、シェルのメタ文字との相互作用に注意が必要です。正規表現で
*や[などを使うときは、必ずクォートで囲んでシェルに解釈されないようにすることが基本です(例:grep "^[a-z]*$" fileとダブルクォーテーションやシングルクォーテーションで囲む)。また、変数をパターンに使う場合も同様で、例えばbash pattern="^foo*bar$"; grep -E "$pattern" fileのようにすると、変数内の*が意図通り解釈されます(クォートしないとスペースがある場合にフィールド分割されたり、*がファイル名展開されたりする可能性があります)。さらに、ユーザ入力由来の文字列をそのままgrepの正規表現に渡すと、思わぬメタ文字が含まれていて正規表現エラーや過剰マッチを起こすことがあります。その場合、安全策としてgrep -Fでリテラル検索にするか、正規表現特殊文字(.や*等)をエスケープしてから渡す必要があります。例えば、bashには${var//\*/\\*}のような置換でアスタリスクをバックスラッシュ+アスタリスクに変換するといった方法があります。grep自体に正規表現を無効化するオプションは-F以外にはありませんので工夫してください。 -
パイプラインとの組み合わせ: grepは単体でも活用されますが、他のコマンドと組み合わせて使うことで真価を発揮します。
grepで絞り込んだ結果をさらにgrepで絞る、というようにパイプで繋げばAND条件のフィルタも可能です(例:grep "ERROR" app.log | grep -i "database"とすれば、「ERRORを含み、なおかつ大文字小文字区別せず'database'を含む」行)。また、cutやawkで必要部分だけ取り出したり、sortやuniqで集計したりと、データ処理パイプラインの一部としてgrepを使いこなすこともできます。ベストプラクティスとして、単一のgrepコマンドで済む処理は極力一回で済ませる(不要なパイプは追加のプロセス実行コストになる)一方、可読性や柔軟性のためにパイプを利用することも適宜判断して行うと良いでしょう。 -
特殊なファイルの扱い:
/procのような特殊FSや巨大ファイル、バイナリ混じりのファイルを扱う場合、grepの動作に注意します。前述の-D skipや-Iで対処できるケースもありますが、例えば非常に長い行を持つファイル(数百万文字連続など)だとgrepのバッファが溢れる可能性があります。GNU grepはかなり大きな入力にも対応しますが、極端な場合はPCRE使用時にスタックオーバーフローを起こすこともあり得ます(特に複雑な正規表現+長大行)。実務ではあまり遭遇しませんが、もしgrepが遅かったりメモリを大量消費する場合は、検索対象を分割したり他のアプローチも検討してください。
注意点・落とし穴 (Pitfalls and Gotchas)
grepを使う上で、初心者が陥りやすいミスや注意すべき点を挙げます。
-
パターンのクォート忘れ: 先にも触れましたが、検索パターンに正規表現特殊文字や空白が含まれているのにクォートで囲まないケースは頻出する誤りです。例えば、
grep ^Error log.txtのように書くとシェルは^を特殊扱いしないものの、パターンに空白があると別引数と認識してしまいますし、grep [a-z] fileは想定通りアルファベット小文字を探しますが、grep [a-z(閉じ角括弧忘れ)などとするとシェルが[から始まるものをファイル名展開しかねません。対策: 常にパターン全体をシングルクォート'pattern'で囲む習慣をつけると安全です(シェル変数やコマンド置換を含めたい場合はダブルクォートでも可)。特に*や?を含むパターンはシェルにワイルドカードとみなされるため、必ずクォートしましょう。 -
意図しない正規表現解釈: grepのパターンは正規表現とみなされるため、
.や[]、(などを含むと特殊な意味になります。リテラルなピリオドやブラケットを探したい時にそのまま書いてしまうと、例えば.は「任意の1文字」にマッチしてしまいます。例:grep "a[b]c" fileは「a」「b」「c」という3文字の連続ではなく、「a」「任意の文字」「c」にマッチしてしまいます(bは正規表現では文字クラスを囲む[]の中にあり、それ自体がマッチ対象文字の集合だが中に1文字しかないので結果的にa b cの並びにしかマッチしませんが、grep "a[b]c"は実際には"abc"にのみマッチします。この例ではたまたま意図と同じですが、grep "[a-z]"のようなケースでは角括弧はリテラルではなく「文字クラス」になる点が重要です)。対策: 特殊文字を含むパターンで、正規表現としてではなく文字列そのものを探したい場合は-Fを使うか、都度\.や\[のようにエスケープします。よくあるのは、例えば正規表現エンジンに馴染みが無い人が\dや?をそのまま書いてしまい、「grepが動かない(エラーになる)」となるケースです。POSIX grepには\dというエスケープシーケンスはありませんし、?はBREではエスケープしないと普通の文字、EREでは直前の文字の0回/1回なので、思った動きになりません。無効なエスケープシーケンスはgrepではそのままリテラル扱い(例えば\dはdと等価)になったりするため、自分の書いたパターンがgrepにどう解釈されるかを正規表現の基礎知識に照らして考える習慣を付けましょう。 -
変数や正規表現のエスケープ: シェルスクリプトで、たとえばユーザ入力を変数
$queryに入れてgrep検索する場合、その中に.や*が含まれると意図せず正規表現として機能してしまいます。例えばユーザが.だけ入力したら全行にマッチしてしまう、という具合です。対策: 前述の通り、ユーザ入力はgrep -Fで検索するか、安全にエスケープしてから使うといった防御的プログラミングが必要です。また、検索パターンとしてハイフンで始まる文字列(例:-DEBUGを探したい)を直接渡すとgrepはそれをオプションと解釈して「無効なオプション」とエラーになります。解決策: その場合は必ずgrep -e "$pattern"のように-eで明示的にパターンとして指定してください。 -
マッチ結果の行数と出現数の誤解:
-cオプションは各ファイルごとのマッチした行の数を出力します。同じ行にパターンが複数回含まれていてもカウントは1にしかなりません。これはgrepの基本仕様ですが、「合計何回出現したか」と勘違いして-cを使う人がいます。対策: もし「パターンの出現回数」を知りたい場合は、grep -o "pattern" file | wc -lのように、-oで全部抽出してから行数を数える方法があります(このとき複数マッチが1行にあってもそれぞれ抽出され別行になるため、出現数を正確にカウントできます)。 -
grep特有の挙動の理解不足: 例えば、grepの検索は基本的に1行単位で完結します。正規表現で改行を跨いだマッチ(複数行にまたがるパターン)は通常できません。PCREの
-Pでもデフォルトでは複数行モードでないため同様です((?s)や(?m)といったフラグ指定は可能ですが、あくまでバッファは行ごとに処理)。つまり、「段落単位で特定の文字列が含まれる段落を抜き出したい」といった場合、grep単独では難しく、awkやperlに頼る必要が出てくるかもしれません。この1行原則を理解しておかないと、「なんで期待通りに抽出できないんだろう?」とハマることになります。 -
環境ごとのgrepの違い: GNU grepとBSD grepで微妙に挙動が異なる場合があります。実行環境を切り替えるとオプションが使えなかったり、正規表現の解釈が異なる可能性があります。例えば、BSD grepの正規表現実装は**<や>**で単語境界(GNUでは未サポート)をサポートしますが、GNU grepでは
\<はただの<と解釈されます。逆にGNU grepの-PはBSD grepにはありません。対策: スクリプト内で使うgrepはできればGNU grepに統一し、そうでなければcommand -v ggrep(GNU grepが別名で入っていないか)を確認する、docker等で環境を統一するなど工夫も考えられます。またmacOSではHomebrewでgrep(gnu grep)がインストール可能で、通常ggrepとしてコマンド名が提供されます。環境差異を吸収するために一旦変数にgrepコマンド名を代入して使う(GNU優先でなければaliasやfunctionでラップする)方法もあります。 -
出力量が多すぎる場合の対処: grepの結果が非常に多く、ターミナルが流れてしまう場合があります。
| lessや| moreでページャーに渡すのは一つの解決策です。また--colorを使っている場合、less -Rオプション(コントロールシーケンスをそのまま表示する)でカラー表示のまま閲覧できます。別の方法として、出力をファイルにリダイレクトし、後でエディタやページャーで確認する手もあります。grep自体は大きな出力を自動でページングしたりはしないので、手動で対応しましょう。
以上の注意点を念頭に置きつつ、grepを使いこなしていけば、日々のテキスト操作やデータ解析が非常に効率良くなるでしょう。では、最後に理解度を深めるための練習問題をいくつか解いてみましょう。
練習問題 (Exercises)
あなたの理解を確認するため、以下にいくつかgrepコマンドに関する練習問題を用意しました。難易度は基本から応用まで様々です。各問題の解答例はこの節の後にありますので、まずは自分で考えてみてください。
names.txtというファイルから、"Alice" という名前を含む行を大文字小文字を区別せず抽出するコマンドを書いてください(例:"ALICE"や"Alice"もマッチさせる)。error.logというファイルに記録された行のうち、"ERROR"という単語を含まない行が全部で何行あるかを数えるコマンドを書いてください。access.logファイルから、HTTPステータスコードの404または500を含む行だけを抜き出し、行番号付きで表示するコマンドを書いてください。- カレントディレクトリとそのサブディレクトリ内のすべてのPythonファイル(
.py)から、文字列 "TODO" を含むファイルを探し、そのファイル名のみを一覧表示するコマンドを書いてください(大文字小文字は区別しないで検索するものとします)。 data.txtというファイルから、IPv4アドレス(例: 192.168.0.1 のような形式)にマッチする部分をすべて抽出して表示するコマンドを書いてください(※grepの正規表現機能を使い、-oオプションで一致部分のみ表示してください)。server.logというログファイル中に含まれる行で、"FAIL"という文字列を含む行について直前2行と直後1行もあわせて表示したいです。そのためのgrepコマンドを書いてください。animals.txtというファイルには動物の名前一覧が書かれています。この中から**"cat"という単語が他の文字にくっついていない単独の単語として現れる**行だけを表示するgrepコマンドを書いてください(例: "cat" はマッチ、"caterpillar" はマッチしない)。
解答 (Solutions)
-
names.txtから"Alice"を大文字小文字無視で検索:
grep -i "alice" names.txt-iオプションで大文字小文字を区別しない検索を行います。これにより、ALICEやAliceなどどんな大小写でもマッチします。 -
"ERROR"を含まない行数のカウント:
grep -cv "ERROR" error.log-vでパターンにマッチしない行を選択し、-cでその行数を数えます。このコマンドの出力結果が、error.log内で"ERROR"を含まない行の総数となります。 -
access.logから404または500を含む行を行番号付きで表示:
grep -E -n "404|500" access.log-Eを指定して拡張正規表現のOR(|)を使用しています。また-nで行番号を付けています。これにより、ログファイル内の404または500エラー行が行番号付きで抽出されます。例えば出力は15:404 Page Not Foundのようになります。 -
サブディレクトリ含む全
.pyファイルから"TODO"を含むファイル名を一覧:grep -R -l -i --include="*.py" "TODO" .-Rで再帰検索、-lでマッチしたファイル名のみ表示、-iで大小無視、さらに--include="*.py"でPythonファイルに限定しています。カレントディレクトリ以下で"TODO"という語が見つかったすべての.pyファイルがリスト表示されます。 -
IPv4アドレスの抽出:
grep -E -o '([0-9]{1,3}\.){3}[0-9]{1,3}' data.txtこの正規表現パターンは「1~3桁の数字とピリオドのセットを3回繰り返し、最後に1~3桁の数字」でIPv4形式にマッチさせています。
-oにより一致部分(IPv4アドレスそのもの)のみを抽出します。data.txt内のIPアドレスが全て列挙されるでしょう(※マッチは0~255の範囲以外も形式上拾いますが、この範囲チェックはgrepでは難しいため一旦形式マッチとしています)。 -
"FAIL"行とその前後を表示(コンテキスト表示):
grep -B2 -A1 "FAIL" server.logこれで"FAIL"を含む行と、その直前2行・直後1行が表示されます。複数箇所でFAILが発生していれば、コンテキストブロックごとに
--で区切られて出力されます。 -
"cat"単独単語として含む行を表示:
grep -w "cat" animals.txt-wオプションにより、単語境界に囲まれた"cat"のみマッチします。したがって"cat"だけの行や、"dog and cat"のように空白区切りでcatが現れるケースは表示されますが、"caterpillar"のように文字列の一部として連続している場合は除外されます。
各問題の解答コマンドを実行すると、想定通りの結果が得られることを確認してください。これらを応用することで、grepを様々なシチュエーションで使いこなすことができるでしょう。