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:

  1. Core functionality and purpose
  2. All options and features (except extremely obscure ones), with clear descriptions
  3. Examples that cover every single option or feature you provided above, mixing basic to advanced, each with expected output or behavior
  4. Common use cases and best practices
  5. 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.

You·today at 2:05 am

はい、ご指示の内容を理解しました。次回から、ご要望に沿った形式で日本語による詳細な解説と例を提供いたします。

o3 Deep Research·today at 2:05 am

awk

You·today at 2:08 am

AWKコマンド徹底解説

概要

AWK(オーク)はテキスト処理とデータ分析のためのスクリプト言語兼コマンドです。Unix系システムで標準的に利用でき、フィルタ処理やレポート生成によく使われます。名前の由来は作者であるアルフレッド・エイホ (Aho)、ピーター・ワインバーガー (Weinberger)、ブライアン・カーニハン (Kernighan) の姓の頭文字から取られています。AWKコマンドは入力テキストを行単位(レコード)で読み込み、各行を空白などで区切られたフィールドに分割して処理します。プログラム内で行ごとに条件(パターン)を評価し、真にマッチした場合に対応する処理(アクション)を実行します。このようなパターンアクションの組を指定することで、特定の行を抽出したり変換したり、集計結果を計算したりできます。

AWKプログラムはpattern { action }という形式で記述し、入力全体に対して暗黙のループを行います。各入力行(レコード)がパターンに一致すると、その行に対して波括弧内のアクションを実行します。パターンを省略するとすべての行に対してアクションを実行し、アクションを省略するとデフォルトでその行($0)を表示します。例えば、単に/error/というパターンだけ指定すれば、"error"という文字列を含む行がすべて標準出力にプリントされます(これはgrepのような動作になります)。逆にパターンを指定せずに{ print $1 }というアクションのみを書けば、全行について1列目(最初のフィールド)を表示します。

また、**BEGINEND**という特殊なパターンを使うことで、最初のデータ行を処理する前と全てのデータ処理が終わった後にそれぞれ一度だけ実行される初期化処理や終了処理を記述できます。例えばBEGINブロックでカウンタを初期化し、通常行でカウントを増加、ENDブロックで結果を出力するといった使い方が可能です。AWK言語は変数や計算、文字列操作、配列(連想配列)などの機能を備え、単純な一行コマンド(ワンライナー)から複雑なテキスト処理スクリプトまで対応できる強力なツールです。

コマンドラインオプションと使い方

AWKコマンドの基本書式は次のとおりです:

awk [オプション] 'プログラム' [入力ファイル...]

'プログラム'部分にAWKスクリプト(パターンとアクションの組)をシングルクォートで囲んで指定します。入力ファイルを指定しない場合、標準入力からデータを読み込みます。ここでは主要なオプションとその機能について説明します(特殊な実装依存のオプションを除きます)。

  • -F <区切り>: フィールド区切り文字を指定します。デフォルトでは空白文字(スペースやタブ、改行の連続)でフィールド分割しますが、-Fを使うと区切り文字を任意の文字列や正規表現に変更できます(例えば,:など)。
    例: 区切り文字をコロンに設定し、各行の1フィールド目を表示します。

    $ echo "user:123:/home/user" | awk -F ":" '{ print $1 }'
    user
    

    上記では-F ":"によってフィールド区切りが「:」となり、$1`は"user"を指します。[POSIX]

  • -f <スクリプトファイル>: AWKプログラムを外部ファイルから読み込んで実行します。スクリプトが長くなる場合や再利用したい場合、プログラムをファイル(例えばscript.awk)に書き、このオプションで指定できます。複数の-fオプションを使って複数のファイルからプログラムを読み込むことも可能です(指定したファイルの内容は連結され単一のプログラムとして実行されます)。
    例: 外部ファイルscript.awkにプログラムを用意し、それを適用します。

    $ cat script.awk
    { print $NF }
    $ echo "one two three" | awk -f script.awk
    three
    

    この例では、スクリプトファイル内のプログラム{ print $NF }が実行され、各行の最後のフィールド($NF)が表示されます。[POSIX]

  • -v <変数>=<値>: AWKプログラム実行前に内部変数へ値を設定します。シェル変数の値をAWKに渡したい場合によく使われます。-vオプションは複数回指定でき、そのたびに異なる変数に値を代入できます(ただし1回の-vでは1つの変数しか設定できません)。この変数割り当てはBEGINブロックの前に行われるため、BEGIN内でも利用可能です。
    例: シェル変数からしきい値を渡し、その値以上の数値を持つ行だけを表示します。

    $ threshold=10
    $ printf "8\n15\n12\n" > numbers.txt    # テスト用の入力ファイルを準備
    $ awk -v thr="$threshold" '$1 > thr { print $1 }' numbers.txt
    15
    12
    

    上記ではシェル変数thresholdの値10をthrというAWK変数に渡し、パターン$1 > thrによって1列目が10より大きい行のみをプリントしています。[POSIX](注意: -vで組み込み変数を設定するとAWK処理中に上書きされることがあるので推奨されません (www.gnu.org))。

  • --: オプションの終了を示します。これ以降のコマンドライン引数はファイル名や変数割り当てとして扱われ、-で始まっていてもオプションとは見なされません (www.gnu.org)。ファイル名がハイフンで始まる場合や、コマンドラインに-v-Fといった文字列が出現する可能性がある場合に--で明示的に区切ると安全です。
    例: ファイル名-data.txtを入力として指定する場合:

    $ awk '{ print NR, $0 }' -- -data.txt
    

    --がないと-data.txtをオプションと誤解されますが、付けることで正しくファイル名と認識させています。[POSIX]

  • -W <オプション> / --<オプション長>: 実装依存の追加オプションを指定します。-WはPOSIXで予約された形式で、具体的な内容はAWKの実装によって異なります (www.gnu.org)。GNU版のAWKである**gawk [GNU]**では多くの拡張オプションが提供されています。代表的なものを挙げます:

    • --version (または-W version): 使用しているAWKのバージョン情報を表示して終了します。
      例:
      $ awk --version
      GNU Awk 5.0.1, API: 3.0 (GNU MPFR 4.0.2, GNU MP 6.2.0)
      ... (以下略)
      
    • --help (または-W help): AWKコマンドの使用方法のサマリを表示します (www.gnu.org)。主要なオプションの一覧が表示されます。
    • --lint: プログラムを解析し、移植性に問題がありそうな構文や非推奨事項があれば警告を表示します。スクリプトのデバッグやPOSIX準拠チェックに有用です(オプション引数で警告レベルを指定可能、例: --lint=fatalで警告をエラー扱い)。[Non-POSIX][GNU]
    • --posix: GNU拡張を無効にしてPOSIX標準モードで動作させます (www.gnu.org)。古いAWKや他環境との互換性テストに利用します。[Non-POSIX][GNU]
    • --traditional: 初期の伝統的なAWKの動作に近づける互換モードです。こちらも一部GNU拡張が無効になり、古いバージョン特有の挙動(例えば正規表現区切りの挙動など)になります。[Non-POSIX][GNU]
    • -W interactive: 出力を行単位でフラッシュし、入力を行バッファにします。対話的な用途で逐次出力したい場合に使用します(デフォルトではAWKはパイプ経由の出力をバッファリングするため、リアルタイム性が必要な場合に指定)。[Non-POSIX][GNU]
    • -W dump: AWKプログラムを内部表現(仮想マシンの命令列)にコンパイルした結果を表示して終了します。デバッグ目的の特殊機能です。[Non-POSIX][GNU]
    • -e 'プログラム' / --source 'プログラム': コマンドラインで与えるAWKプログラム断片を複数指定するためのオプションです。-e毎に一部のプログラムを渡し、それらを順につなげて実行できます (www.gnu.org)。複雑なワンライナーを書く際に、シェルの引用符絡みの煩雑さを避けてプログラムを分割できる利点があります。[Non-POSIX][GNU]
    • -i <ライブラリファイル> / --include <ライブラリファイル>: AWKプログラム内にライブラリファイルを組み込んで実行します。内容的にはプログラム中で@include "ファイル名"と書くのと同等で、-fとの違いは同じファイルを複数回インクルードしようとした際に一度しか読み込まれない点などがあります (www.gnu.org) (www.gnu.org)。共有関数などを切り出したファイルを再利用するのに便利です。[Non-POSIX][GNU]
    • -E <スクリプトファイル> / --exec <スクリプトファイル>: -fに似ていますが、このオプション以降に指定された引数はすべて入力ファイルと見なされ、さらにコマンドライン上での変数代入指定(-vではなくプログラムの後ろに書く形式の変数指定)が無効になるという違いがあります (www.gnu.org) (www.gnu.org)。Shebang (#!) にAWKを直接指定してスクリプトを実行する場合などに有用です。[Non-POSIX][GNU]

※上記以外にもGNU Awkには--dump-variables(グローバル変数一覧の出力)や--trace(実行時の内部命令トレース出力)、--gen-pot(翻訳メッセージ用テンプレート生成)、--sandbox(危険な機能を無効化してセキュアに実行)など多数の拡張オプションがあります。特殊な用途のものが多いためここでは詳細説明を省きますが、必要に応じてawk --helpで確認してください。一般的な使用では-F-f-vといった基本オプションを押さえておけば十分です。

AWK言語の主要機能

パターンとアクションの基本

AWKプログラムは一連の「パターン と アクション」の組で構成されます (en.wikipedia.org)。基本構文は次のとおりです:

パターン { アクション }
パターン { アクション }
...(必要なだけ複数行で記述)...

各パターンは、その行(レコード)を処理するかどうかの条件を表します。パターンには以下の種類があります:

  • /正規表現/: スラッシュで囲んだ正規表現リテラルをパターンにできます。入力行がその正規表現にマッチした場合にアクションを実行します。例えば、パターン/error/は行に"error"という文字列を含む場合に真となります。正規表現はAWKではデフォルトで拡張正規表現(ERE)が使われ、^$で行頭・行末、.+|などが利用できます(POSIX ERE準拠)。[POSIX]
    例: "error"を含む行番号と内容を表示するプログラム

    $ awk '/error/ { print NR, $0 }' logfile.txt
    15 error: ファイルが見つかりません
    27 critical error: メモリ不足
    

    上記のAWKプログラムでは、/error/にマッチした行だけが処理され、print NR, $0により行番号と行内容が出力されています。

  • 式(条件式): パターンに比較演算や計算式を記述し、その評価が真(非ゼロ/非空)になった場合にマッチと見なします。典型例はフィールドの値による条件指定です。例えば、パターン$3 > 100は3番目のフィールドが100より大きい行のみを対象にアクションを実行します。文字列比較も可能で、$1 == "ERROR"のように==で等価比較できます(オペランドが数値か文字列かはAWKが自動判別します)。[POSIX]
    例: 2列目が"NG"でない行だけを表示するプログラム

    $ awk '$2 != "NG" { print $0 }' data.txt
    

    この例ではパターン$2 != "NG"が条件式になっており、2列目が"NG"に等しくない行のみがprintで出力されます。

  • BEGIN/END: 前述のとおり、BEGINENDは特殊なパターンです。BEGINブロック内のアクションは最初の入力行を処理する前に一度だけ実行され、ENDブロック内のアクションは全ての入力処理が終わった後に一度だけ実行されます (en.wikipedia.org)。BEGINは主に初期化処理やヘッダー出力に、ENDは集計結果の出力やクリーンアップに使われます。[POSIX]
    例: 行数をカウントするプログラム(BEGINでカウンタ初期化、ENDで結果表示)

    $ awk 'BEGIN { count=0 } { count++ } END { print "行数:", count }' file.txt
    行数: 42
    

    BEGINブロックでcount変数を0に初期化し、各行でcount++によって1ずつ加算、ENDブロックで合計を表示しています。

  • 範囲パターン: パターン1, パターン2の形式で指定し、あるレコードがパターン1にマッチしたらそこからパターン2にマッチするレコードまで連続してアクションを実行します (en.wikipedia.org)。範囲の終わり(パターン2に一致する行)が現れたら、その行も処理して範囲はリセットされ、その後再びパターン1の一致を待ちます。
    例: セクション開始行「START」から「END」という行までをまとめて出力するプログラム

    $ awk '/START/,/END/ { print $0 }' input.txt
    

    このAWKは、"START"という行に遭遇すると出力を開始し、"END"という行までの全行を出力します。"END"行が出力された時点で一旦範囲がリセットされ、次の"START"が現れれば再び出力を開始します。

パターンは上記のように柔軟ですが、何も指定しない場合は常に真と見なされます。またアクション部分(波括弧 {} 内)を省略するとデフォルト動作は{ print $0 }(現在の行を表示)です (en.wikipedia.org)。そのため、単にパターンだけを書いた場合はマッチした行を表示するフィルタになり、逆にパターンを空にしてアクションにprintを書けば全行を表示するだけの処理になります。これらのデフォルト動作を理解しておくことで、短いAWKワンライナーを書く際に余分な記述を省けます。

フィールドとレコードの処理

AWKは入力テキストをレコード(既定では1行)ごとに読み込み、さらに各レコードをフィールドに自動分割して処理します (en.wikipedia.org)。

  • レコード区切り (RS): レコード区切り文字を表す組み込み変数RSによって、何をもって1レコードとみなすかを指定できます。デフォルトのRSは改行"\n"で、入力を1行ずつレコードとして処理します。RSに別の文字列を設定すると(例えば空行区切りにしたい場合はRS=""と設定)、その文字や文字列でレコードが区切られます。[POSIX]
    例: RSを空行(二重改行)に設定すると、空行ごとに区切られた段落単位でレコードとして読み込むようになります(GNU Awkでは正規表現も指定可 (www.gnu.org)ですがPOSIX AWKでは1文字のみ)。[Non-POSIX][GNU]

  • フィールド区切り (FS): 各レコード内でフィールドを区切る区切り文字(または正規表現)を指定する組み込み変数がFSです。デフォルトのFSは空白 " "(スペース1文字)ですが、特殊な挙動があります。FSが空白の場合、AWKは連続する空白やタブを1つの区切りと見なし、行頭の空白は無視してフィールド分割します (www.gnu.org)。つまり、空白がいくつ重なっていてもそれらを区切り文字としてまとめ、先頭の空白はフィールドを生まないということです。このため、データが空白区切りで整形されている場合には都合がよいですが、逆に連続する空白を区別したい場合先頭に空欄フィールドがありうる場合はFSを明示的に設定する必要があります。FSはコマンドラインの-Fオプションで指定するほか、AWKプログラム中のBEGINブロックでFS=":"のように代入して設定することも可能です。[POSIX]
    例: CSV(カンマ区切り)ファイルを処理する場合、BEGIN{ FS="," }やオプション-F,でフィールド区切りをカンマに指定できます。

    $ echo "Alice,30,Tokyo" | awk -F, '{ print "名前=" $1 ", 年齢=" $2 }'
    名前=Alice, 年齢=30
    

    例: データ中の連続空白を区別する例(デフォルトFSとの違い)

    $ echo "A  B   C" | awk '{ print $1, $2, $3 }'
    A B C
    $ echo "A  B   C" | awk 'BEGIN{ FS=" " } { print $1, $2, $3 }'
    A B 
    C
    

    1つ目のAWK(FSデフォルト)では"A"と"B"の間の2つの空白、および"B"と"C"の間の3つの空白はいずれも単一の区切りと見なされ、結果として3つのフィールド"A","B","C"が得られています。2つ目のAWKではFSを明示的にスペース1文字に設定したため、空白は区切り文字としてそのまま扱われ、"A"と"B"の間に空白の空フィールドが一つ生まれています("B"と"C"の間は空フィールド2つ)。このように、FSをデフォルトの空白から変更すると空白の扱いが変わる点に注意してください。

  • フィールド参照 (1,1, 2, ..., 0):0)**: `1は現在レコードの第1フィールド、2は第2フィールドというように、2`は第2フィールド…というように、`<番号>で各フィールドにアクセスできます。0`はその行全体(全文字列)を表す特別なフィールドです ([en.wikipedia.org](https://en.wikipedia.org/wiki/AWK#:~:text=expression%20that%20is%20true,action%20structure%20as%20sed))。例えば、`{ print 1, 3 }`と書けば各行について1列目と3列目をスペース区切りで出力します。また、`NF`(後述)の値を使って`NFと指定すると最終フィールド、$(NF-1)`とすれば最後から2番目のフィールド、といった相対指定も可能です。[POSIX]**

  • フィールドの再構築: AWKでは一度読み込んだフィールドを変更することもできます。例えば$2 = "変更後"のように代入すると、内部的にそのフィールド値が変わり、$0(全体)も自動的に再組み立てされます。再組み立て時には現在のOFS(出力フィールド区切り、デフォルトは空白)が区切り文字として用いられます。この機能により、入力フィールドを書き換えて出力するという操作も簡単に行えます。
    例: 2列目を"REDACTED"に置き換えて出力

    $ echo "user1 pass123" | awk '{$2 = "REDACTED"; print $0 }'
    user1 REDACTED
    

    $2を書き換えたことで$0に反映され、結果として2フィールド目がマスクされた行が出力されています。

  • 組み込み変数 (NF, NR など): AWKにはレコードやフィールドに関する情報を保持する多数の組み込み変数があります。特に重要なものを紹介します:

    • NF: (Number of Fields)現在レコードのフィールド数を表します。その行にフィールドがいくつあるかを示す整数で、空行であれば0、カンマ区切りCSVで5項目あればNF=5となります。NFは各レコード毎に更新され、また$0を書き換えた場合はそれに応じて変化します。
    • NR: (Number of Records)現在までに読み込んだレコード数(行番号に相当)を保持します。全入力を通しての通し行番号で、複数ファイルを処理している場合も通算されます。最初の行でNR=1、次の行で2と増えていきます。
    • FNR: 現在のファイル内でのレコード番号です。複数ファイルを処理している場合に、それぞれのファイルで1からカウントしなおされます。ファイルが切り替わるとFNRはリセットされ、NRは継続する点がNRとの違いです。
    • FILENAME: 現在読み込み中のファイル名が入ります。標準入力を処理中は空文字列になる実装もあります。複数ファイル処理時にどのファイル由来かを判別するのに使います。
    • ARGCARGV: コマンドライン引数の数と内容です。ARGVは配列で、ARGV[0]が"awk"コマンド名、以降にファイル名やオプションなどが格納されています。AWKプログラム内でARGVを操作して要素を削除すると、そのファイルの入力をスキップするといった高度な制御も可能です(上級テクニック)。[POSIX]
    • FSRS: 入力のフィールド区切りとレコード区切りを表す変数で、既に説明したとおりデフォルトはFSが空白、RSが改行です。[POSIX]
    • OFSORS: 出力のフィールド区切りとレコード区切りを表す変数です。print文で複数項目をカンマで並べたとき、間に挿入される文字がOFSで、print文自体が出力の終わりで付加する文字がORSです。デフォルトではOFSはスペース " "、ORSは改行 "\n"です。例えばOFS=","と設定すればprint $1, $2はカンマ区切りで出力します。[POSIX]
    • OFMT: 数値を文字列に変換するときのデフォルト書式を指定します(printfではなくprintで数値を出力する際などに適用)。デフォルトは"%.6g"で、有効数字6桁の浮動小数点表記になります。必要に応じてOFMT="%.2f"のように変更可能です。[POSIX]
    • CONVFMT: 文字列を数値に変換する際の書式です。通常はOFMTと同じ値が入っています。[POSIX]
    • IGNORECASE: 大文字小文字の区別を無視するかを制御する変数です。GNU AwkではIGNORECASE=1と設定すると、正規表現のマッチやindex関数など文字列比較が大文字小文字を区別しなくなります。デフォルトは0(区別する)です。[Non-POSIX][GNU]
    • ENVIRON: 環境変数を読み取るための連想配列で、シェル環境変数がENVIRON["HOME"]のようにキー指定で取得できます。POSIX AWKには存在しませんが、多くの実装(gawkやmawkなど)でサポートされています。[Non-POSIX]
    • RSTARTRLENGTH: match()関数(後述)を使った際に、直近でマッチした位置と長さを保持します。マッチがなければRSTART=0、RLENGTH=-1になります。

    以上の組み込み変数はプログラム内で直接参照・代入が可能です。例えば、NR==1のときだけヘッダを出力する、といった条件分岐に使えます。例: 複数ファイルの行を処理しながらファイル名と行番号を表示するワンライナー:

    $ awk '{ print FILENAME ":" FNR ": " $0 }' file1.txt file2.txt
    

    これにより、出力の各行はファイル名:行番号: 元の内容という形式になります。複数ファイルを扱う際にFILENAMEFNRを利用すると、どのファイルの何行目か識別できるので便利です。

算術演算と文字列操作

AWKは数値計算と文字列操作の両方に強力な機能を備えています。特に数値と文字列の型変換は自動で行われ、文脈に応じて適切に解釈されます(例えば、数値演算が期待される場面で文字列が使われると可能な限り数値に変換されます)。

  • データ型と自動変換: AWKには明示的な型宣言はなく、数値(内部的には倍精度浮動小数点)か文字列かはコンテキストによって決まります。例えば、変数xに文字列"42"が入っていても、x + 1とすれば数値42に変換されて43となります。逆に数値を文字列連結するとき(後述の文字列結合)には自動的に文字列化されます。注意点として、AWKの数値は浮動小数なので整数同士の割り算5/2も2ではなく2.5になります。また非常に大きな整数は有効桁数を超えると丸め込まれる(精度を失う)可能性があります。

  • 算術演算子: + - * / %といった基本的な算術演算子が使えます。%は剰余(モジュロ)で、被演算子を整数に丸めてから剰余計算します。べき乗^演算子で表し、例えば2^3は8を計算します。符号反転の単項-やインクリメント/デクリメント(++および--、前置・後置ともに使用可)もサポートされています。[POSIX]
    例: 基本的な算術計算

    $ echo "5 2" | awk '{ print $1+$2, $1-$2, $1*$2, $1/$2, $1%$2, $1^$2 }'
    7 3 10 2.5 1 25
    

    上記では入力行「5 2」に対して、それぞれ加算=7、減算=3、乗算=10、除算=2.5、剰余=1、累乗(5の2乗)=25を計算しています。

  • 比較演算子: ==, !=, <, >, <=, >=の比較演算が利用できます。AWKでは両オペランドが数値として解釈可能なら数値比較、そうでなければ文字列比較になります(例えば"9" < "10"は数値的には9 < 10で真ですが、文字列としては「9」は「10」より後になるため偽になります)。文字列比較を明示したい場合は("9" + 0)のように数値化したり、逆にsprintf("%s", x)で文字列化するテクニックがあります。[POSIX]

  • 論理演算子: 論理ANDは&&、論理ORは||、論理NOTは!です。これらはC言語と同様、短絡評価が行われます。複数条件を組み合わせる際に使用します。[POSIX]

  • 正規表現マッチ演算子: 文字列が正規表現にマッチするかを判定する演算子として~!~があります (en.wikipedia.org)。左辺を文字列、右辺を正規表現リテラルまたは変数で、str ~ /regex/str/regex/にマッチすれば真、!~はその逆です。パターン部分で/regex/と書く以外に、この演算子を使えば条件式の中で任意の文字列やフィールドを対象にマッチ判定ができます。[POSIX]
    例: 3列目に"ERROR"を含む行のみ処理する(大文字小文字区別なし)

    $ awk 'toupper($3) ~ /ERROR/' file.txt
    

    上記ではtoupper($3)で3列目の文字列を大文字化し、その中に"ERROR"が含まれるか~ /ERROR/で判定しています(toupper()はGNU Awkの拡張関数 (en.wikipedia.org)なので他環境では代わりに/[Ee][Rr][Rr][Oo][Rr]/のような正規表現を使います)。[Non-POSIX][GNU]

  • 文字列結合: AWKには文字列連結用の演算子がありませんが、空白の隣接によって文字列を連結できます (en.wikipedia.org)。例えば変数a="Hello"b="World"の場合、a bと書くと"HelloWorld"となります。数値もこの形で連結すると文字列に変換されます。文字列リテラルを連結する場合は直接 "Hello ""World" のように隣接させます(結果は"Hello World"。リテラル間にスペースがあるとそのままスペースが文字に含まれます)。この暗黙の連結は意図が分かりづらい場合もあるため、複雑な式では(a)(b)のように括弧で囲んで連結したり、明示的にsprintf関数(後述)を使うと読みやすくなります。
    例: 連結の例

    $ awk 'BEGIN { x=10; print "値は" x "です" }'
    値は10です
    

    これは"値は"と変数xと文字列"です"を連結してひとつの文字列にしています。

制御構造

AWKはプログラミング言語として、条件分岐やループなどの制御構造を備えています。基本的な文法はC言語に近い形式です。

  • 条件分岐 (if 文):

    if (条件) {
        アクション
    } else if (条件2) {
        アクション2
    } else {
        アクション3
    }
    

    ifelseによる条件分岐が可能です。else ifを重ねて複数の条件を判定できます。波括弧はアクションが1文の場合省略可能ですが、可読性のため明示的に書く方が無難です。[POSIX]

  • 繰り返し (ループ):

    • while ループ: ```awk while (条件) { 本文 }
      条件が真である間、本文を繰り返します。回数が不明な繰り返しに使用。  
      
    • do...while ループ:
      do {
          本文
      } while (条件);
      
      本文を少なくとも1回実行し、その後条件判定して続行するか決めます。
    • for ループ (C形式):
      for (初期化; 条件; 後処理) {
          本文
      }
      
      C言語風のfor構文で、回数が決まっている繰り返しに便利です。例えばfor(i=1; i<=NF; i++) { print $i }のように使います。
    • for-in ループ (配列用):
      for (キー in 配列) {
          本文
      }
      
      配列(連想配列)のすべてのキーに対して繰り返します。配列の各要素を処理する際に使用します(キーの順序は未定義)。[POSIX]

    AWKのループ内では、break文でループを抜け出し、continue文で以降の処理をスキップして次の反復に移ることもできます。[POSIX]

  • その他の制御文:

    • next: 現在のレコード(行)の処理を中断し、即座に次のレコードの処理に移ります (en.wikipedia.org)。nextに到達すると以降のアクションは実行されず、AWKは次の入力行を読み込んで最初のパターンから判定をやり直します。多段のifで囲むより簡潔に「この行以降はスキップ」を実現できます。[POSIX]
    • nextfile: 現在の入力ファイルの残りをスキップして次のファイルへ進みます。例えば巨大なファイルを途中で打ち切りたい場合に使用します。nextfileはGNU Awkなどで提供される拡張で、POSIX AWKにはありません。[Non-POSIX]
    • exit: 全ての処理を即座に終了し、オプションで終了ステータスを指定できます(例: exit 1)。ENDブロックも実行せずに終了する点に注意が必要です。[POSIX]
    • return: ユーザー定義関数内で指定値を返し、その関数の実行を終了します。[POSIX]

配列(連想配列)

AWKの配列は連想配列であり、整数インデックスだけでなく文字列をキーとして使用できます (en.wikipedia.org)。特にキーを明示指定しない場合も、数値を文字列に変換したものがキーとなるため、通常の数値添字配列のようにも扱えます。

  • 配列の宣言と使用: AWKでは明示的な配列宣言は必要なく、初めて要素に値を代入すれば配列が生成されます。例えば、count["apple"] = 5と代入すればcountという配列の"apple"というキーに5が格納されます。数値キーもdata[1] = "foo"のように代入すれば利用できます。
    例: 単語の出現回数を数えるプログラム(各単語をキー、頻度を値とする連想配列を使用)

    $ awk '{ for(i=1;i<=NF;i++){ word = $i; freq[word]++ } }
           END { for(w in freq) { print w, freq[w] } }' words.txt
    

    上記は各行について全単語を走査し、配列freqで出現回数をカウントしています。ENDブロックで配列の全キーについてループし、それぞれのキー(単語)と値(頻度)を出力しています。このように、未登録のキーに++演算などでアクセスすると自動的に初期値0からインクリメントされます。

  • 要素の削除: 配列要素を削除するにはdelete 配列[key]文を使います。例えばdelete freq["apple"]とするとキー"apple"の要素が配列から取り除かれます。全要素を削除するにはループで各キーを削除するか、配列全体に対してdelete 配列(GNU Awk拡張)を使用します。[POSIX](個々の要素の削除はPOSIX準拠、配列全体の削除は[GNU]拡張)

  • 多次元配列: AWKの配列は多次元を直接サポートしませんが、疑似的に可能です。arr[i,j]のようにカンマで区切って添字を書くと、内部ではi SUBSEP jという一つの文字列キーに変換されます。SUBSEPはデフォルトでは\034という非表示文字なので、衝突しない限りarr[1,2]arr["1\0342"]は同じキーとして扱われます。これを利用して多次元的に使えますが、キーとして連結しているだけなので順序付けなどは手動管理が必要です。GNU Awk (v5以降) では真の多次元配列が導入され、入れ子の配列として扱えるようになりました**[Non-POSIX][GNU]**。

  • 配列の走査順序: for (k in array)でループする際の順序は未定義です。実装によって異なりますが、典型的にはハッシュマップ的な順序(散在順)になります。順序が必要な場合は、キーを別途配列やリストに格納してソートする必要があります。GNU Awkには配列のソートを行う関数asort()asorti()が用意されています**[Non-POSIX][GNU]**。

入出力と組み込み関数

AWKは組み込みで強力な入出力機能と数多くの組み込み関数を持っています。

  • print と printf: 出力にはprint文とprintf関数が使えます。printは与えられた式や文字列を出力し、カンマ区切りで複数指定するとOFSで連結され、末尾にORS(通常は改行)が付加されます。書式指定はしませんが手軽に使えます。一方printfはC言語同様の書式文字列に従って出力します。改行は自動付加されないため必要なら明示的に\nを入れます。
    例:

    $ awk 'BEGIN { x=42.7; print "x =", x; printf("四捨五入すると %.0f です\n", x) }'
    x = 42.7
    四捨五入すると 43 です
    

    1行目はprintで単純表示、2行目はprintfで書式%.0fを用いて小数を整数に丸めて表示しています。

  • ファイル/パイプへの出力: printprintfにリダイレクトを指定することで、出力先を変更できます。print ... > "filename"とすれば指定したファイルに出力し、>>を使えば追記モードになります。また、print ... | "command"と書くとその外部コマンドにパイプで出力を流せます。例えばprint $0 | "sort >> sorted.txt"のようにすれば、現在行を外部のsortコマンドに渡しその結果をファイルに追記する、といったことも可能です。出力先ごとにAWK内部ではファイルやプロセスが開かれた状態になるため、必要ならclose("filename")close("command")で明示的に閉じることもできます(特にループ内で多数の出力先を使う場合にリソース開放が必要)。[POSIX]

  • 標準入力以外からの入力 (getline): 組み込み関数getlineを使うと、現在の入力ストリームとは別に手動で入力を1行読み込むことができます。使い方は次のような形式があります:

    • getline 単独で呼ぶと、次の行を$0に読み込んで成功時1、EOFで0、エラー時-1を返します(NRもインクリメントされます)。
    • getline var と変数を指定すると、次の行を変数varに読み込み(フィールド分割は行わない)、成功時1を返します。NRは変化しません。
    • getline < "ファイル名" と書くと、指定したファイルから1行読んで$0に入れます(最初に開くかシーク位置を覚えていて次以降は続きから読み込む)。
    • command | getline と書くと、外部コマンドの出力から1行読み込みます(プロセスとのパイプを開いて逐次読み取る)。
    • command | getline var とすると、1行を変数に読み込みます。

    getlineは強力ですが、使い方を誤ると無限ループを引き起こしたり、メインの入力処理と干渉する恐れがあります。必要になった場合に詳細を確認して使うことをお勧めします。簡単な利用例としては、例えばファイル名リストを別ファイルから読み込む、シェルコマンドの結果をAWK内で取得する、といったことが可能です。[POSIX]

  • 主な組み込み関数: AWKは数多くの関数を標準で備えています。ここで代表的なものと使用例を挙げます:

    • 文字列長取得: length(s)は文字列sの長さを返します(引数省略時は現在のレコード$0の長さ)。
    • 部分文字列抽出: substr(s, i, n)は文字列sのi文字目からn文字を抽出します(iは1始まり)。nを省略すると末尾まで取得します。
    • 検索: index(s, t)は文字列s中に部分文字列tが現れる位置(1始まり)を返します(見つからなければ0)。match(s, /regex/)sに正規表現がマッチすればRSTARTとRLENGTHを設定し開始位置を返します。
    • 置換: sub(r, repl, s)は文字列sにおいて正規表現rにマッチした最初の部分をreplで置換します(s自体が変更されます)。gsub(r, repl, s)はマッチするすべてを置換します。戻り値は置換した件数です。
    • 大文字小文字: tolower(s)sをすべて小文字に、toupper(s)はすべて大文字に変換した結果を返します。[Non-POSIX][GNU]
    • 分割: split(s, arr, sep)は文字列sを区切り文字sepで分割し、結果を配列arrに1から順に代入します。戻り値は分割数です。sepを省略するとFSが使われます。
    • 数値変換: int(x)は数値xの小数点以下を切り捨てた整数部を返します。rand()は0以上1未満の乱数を返し、srand([seed])は乱数系列のシードを設定します(引数省略時は現在時刻を使い、その時の種を戻り値として返します)。
    • 数学関数: sqrt(x)平方根、sin(x), cos(x), log(x), exp(x)などの数学関数も組み込みで利用できます。[POSIX]
    • 時刻: systime()は1970/1/1からのUnix時間(秒)を返します。strftime(format, timestamp)timestampを指定フォーマット文字列(Cのstrftimeと同様の書式)でフォーマットして返します(timestamp省略時は現在時刻)。これらは日時処理に便利ですが、標準AWKには無くGNU Awkなどの拡張です。[Non-POSIX][GNU]

    例: いくつかの文字列関数の使用例

    $ echo "hello world" | awk '{ print length($0), substr($0,1,5), index($0,"wor") }'
    11 hello 7
    

    入力行"hello world"に対し、length($0)は11(文字数)、substr($0,1,5)は"hello"(1~5文字目)、index($0,"wor")は7("wor"が現れる位置)を出力しています。

    例: subとgsubの違い

    $ echo "apple, banana, cherry" | awk '{ sub(/,/, ";"); print }'
    apple; banana, cherry
    $ echo "apple, banana, cherry" | awk '{ gsub(/,/, ";"); print }'
    apple; banana; cherry
    

    1つ目ではsubにより最初のカンマ1箇所だけがセミコロンに置換されています。2つ目ではgsubによって全てのカンマが置換されました。

    例: split関数によるフィールド分割

    $ echo "2023/12/31" | awk '{
        n = split($0, parts, "/");
        print parts[1] "年", parts[2] "月", parts[3] "日 (", n, "要素)"
    }'
    2023年 12月 31日 (3要素)
    

    splitはこの例で文字列"2023/12/31"を"/"で区切り、配列partsに{"2023","12","31"}を格納しています。戻り値の要素数3をnに受け取り表示しています。

  • ユーザー定義関数: AWKでは自分で関数を定義して使用することもできます。function 名(引数1, 引数2, ...) { ... }という構文で、プログラム中(通常は先頭やBEGINの前など)に関数を記述します。関数内ではreturn 値で呼び出し元に値を返すことができます(void的に使う場合はreturn省略も可)。ユーザー定義関数を使うことで複雑な処理を分割し、コードの再利用性や可読性を向上させることができます。
    例: 平方値を返す関数square(x)を定義して利用

    $ echo "5" | awk 'function square(n) { return n * n }
                     { print square($1) }'
    25
    

    定義したsquare()関数により、入力値5が平方計算され25と出力されています。このように、一度関数を定義すれば複数個所で呼び出すことも容易になります。

以上がAWK言語の主要な構文と機能です。AWKはシンプルな文法でありながら、組み込みの変数や関数、柔軟なデータ操作によって強力なテキスト処理が可能となっています。

よくある用途とベストプラクティス

AWKは多目的に使えるため、そのユースケースは多岐にわたりますが、ここでは特によく使われる用途と活用上のポイントを紹介します。

  • カラム抽出とフォーマット: 指定したフィールドを抜き出したり並べ替えたりする用途によく使われます。例えばログから特定の列(日時やエラーメッセージ部分)だけ取り出す、CSVから必要な列を抽出する、などprint $X, $Yのようなワンライナーで簡潔に実現できます。複数区切り文字が混在する場合でも、正規表現でフィールドを切り出すよう工夫できます。必要に応じてprintfで桁揃えや書式整形を行うと見やすいレポートを作れます。

  • フィルタリング(grep的用途): AWKはパターンマッチに加え、数値比較や複雑な条件ロジックも使えるため、単純なgrepでは難しいフィルタ処理に適しています。例えば「列の合計が一定以上の行だけ表示」や「前後の行も含めてマッチ部分を表示(範囲パターンの利用)」など、柔軟な条件指定が可能です。正規表現もgrep相当の機能を持ち、IGNORECASE変数やtoupper()関数を使えば大文字小文字無視もできます。シェルのパイプと組み合わせれば、grepawksedのような多段処理を一つのAWKでまとめることも可能です。

  • 集計とレポート: AWKはデータの集計や計算にも向いています。例えば特定の列の合計・平均を計算したり、条件ごとのカウントを行ったりといった操作を一回の走査で完結できます。BEGINブロックでヘッダ出力、各行で計算、ENDブロックで結果出力という形で、シンプルなレポートをすぐ作成可能です。複数キーで集計する場合は連想配列を使ってグループ化し、最後に出力するというパターンもよく使われます(例: 部署ごとの合計を計算するなど)。

  • 他のコマンドとの組み合わせ: AWKはパイプライン内で前後のコマンドと組み合わせて使われることも多いです。前段のコマンドの出力を加工したり、後段のコマンドの入力を整形する中間フィルタとして活躍します。例えばpsコマンドの出力をAWKで整形してからgrepで絞り込む、など順序を工夫すると効率的です。AWK自体でほとんどの処理が書ける場合も、場合によってはsedやgrepとの役割分担を考えると簡潔になることもあります。

  • シェルスクリプトでの利用: シェルスクリプト内でAWKを活用する場合、$(awk '...' file)でAWKの出力をコマンド置換して変数に代入したり、そのまま他のコマンドの引数に使ったりできます。AWKで複雑なパースを行い、その結果をシェルで判断するといった役割分担も可能です。スクリプト中で繰り返しAWKを呼び出す場合は、一度のAWKでまとめて処理することを検討したり、大量データを扱う場合はパイプで無駄な中間ファイルを作らないようにするなど、効率を意識すると良いでしょう。

ベストプラクティス & Tips:

  • シングルクォートで囲む: AWKプログラムをコマンドラインに直接書く場合、'プログラム'必ずシングルクォートで囲みます。ダブルクォートだとシェルが中身を解釈してしまい、$やバッククォート、バックスラッシュなどが誤動作します。シングルクォート内ではシェル展開されないため、AWKのコードをそのまま安全に記述できます。どうしてもシングルクォート文字そのものをAWKコード内で使いたい場合は、シェル的に'\''とエスケープするか、プログラムを分割して-eオプションを用いる手法が取れます。

  • シェル変数の受け渡し: AWK内でシェル変数の値を使いたい場合、-vオプションで渡すのが安全です。例えば先述のように-v var="$shell_var"とすれば、シェル変数$shell_varの値がAWK内で変数varにセットされます。シングルクォート内に直接シェル変数を書いてもうまく展開されない(シェルから見ればただの文字列)ため、-vか、あるいはENVIRON配列経由で環境変数にセットしておいてAWK内で読む方法などを使います。

  • 複雑な処理はスクリプトファイルに: AWKワンライナーは強力ですが、あまりに複雑になる場合は無理に一行に詰め込まず、.awkファイルにコードをまとめて-fオプションで読み込む方がベターです。その際はシェルスクリプトからAWKスクリプトを分離でき、構文エラーの検出もしやすくなります。また、複数のAWKスクリプトを組み合わせる場合も-f-iでファイルを分割することで、汎用関数ライブラリとメイン処理を分離する、といった設計ができます。

  • POSIXとの互換性: AWKの方言(実装)は複数あります。LinuxではGNU Awk (gawk)かmawkが多く、BSD系ではawkコマンドはBrian Kernighanの実装(いわゆるナーク,nawk系)が使われています。通常基本的な機能は同じですが、GNU拡張(上で[Non-POSIX]と記した部分など)は他の環境で使えないことがあります。例えばtolower()関数やIGNORECASE変数はPOSIX AWKにないため、移植性が重要なら代替手段をとるべきです。スクリプトを他環境でも動かす必要がある場合、--lintオプションや**--posixモード**で非互換な構文をチェックしたり、実際に別実装(例: mawkやBSD awk)でテストするのが望ましいです。

  • 性能とメモリ: AWKは大抵のテキスト処理で十分高速ですが、非常に大きなデータを扱う場合や複雑な処理ではメモリ使用量に注意します。特に連想配列に大量のデータを溜め込むとメモリ不足になる可能性があります。必要に応じてnextexitで処理を打ち切ったり、集計結果を逐次出力してメモリに溜め込まない工夫が重要です。また、処理速度が問題になる場合、AWKスクリプトを適切にチューニングするか、場合によってはPerlやPythonなど他のツールへの切り替えも検討します(AWKはあくまで効率より手軽さ重視のツールという位置付けです)。

  • デバッグ: AWKプログラムが思ったとおり動かない場合、デバッグ出力を入れて状況を確認するとよいでしょう。print文で変数の中身を随時出力したり、GNU Awkなら--trace--debugオプションもあります。また、一部処理だけ実行したいときは条件でNRなどを限定する(例: NR==10{...})か、入力を一部抜粋したテストファイルで試すなどすると原因を絞り込めます。

注意すべきポイント(ピットフォール)

AWKを使い始める際につまずきやすい点や、経験者でも注意が必要なポイントをいくつか挙げます:

  • 引用符や特殊文字の扱い: AWKプログラムをシェル上で直接書く場合、シェルによる解釈に注意が必要です。特に$\"などはシェルの影響を受けます。基本は先述の通りシングルクォートで囲みますが、シングルクォート内でシングルクォート文字を使うことはできません。この場合、'"'"'(シングルクォート閉じ -> ダブルクォートでシングルクォート文字 -> 再びシングルクォート開始)といったテクニックでエスケープします。複雑な正規表現をAWKに渡す際も、バックslashesやシェルの拡張に食われないよう注意しましょう(例えば正規表現の\1などは二重にバックスラッシュが必要な場合があります)。

  • 数値と文字列の比較: AWKは自動で型変換するため、一見正しそうな比較が意図せず文字列比較になってしまうことがあります。例えばユーザIDのような数字が文字列として扱われている場合、$1 < 100の比較結果が期待と異なることがあります。文字列 "2" と "10" を比較すると文字列順では"10"の方が小さいため、数値として比較したい場合は明示的に変換($1 + 0 < 100など)するテクニックがあります。また、浮動小数のため0.1 + 0.2 == 0.3が偽になるような誤差も起こりえます。必要に応じてsprintfで丸め込んで比較するなど工夫してください。

  • フィールド分割の落とし穴: デフォルトFSは前述のように空白を柔軟に扱いますが、それゆえに意図しないフィールド数になることがあります。特に、入力にタブとスペースが混在していたり、行頭に空白が存在する場合に注意です。行頭の空白は最初のフィールドとならないため、例えば行頭に区切りとしての空欄があるデータ(ExcelからTSVをエクスポートしたとき先頭にタブが入る、など)はデフォルトのAWKではフィールド欠落と見なされます。こういう場合はBEGIN{ FS="\t" }のように明示的にタブのみを区切りに指定すると期待通り扱えます。

  • 正規表現のエスケープ: AWKの正規表現はEREであることに注意してください。BRE(Basic正規表現)と異なり+?がそのまま使えますが、文字クラスや後方参照などはPOSIXの範囲内でしか利用できません。特に波括弧による量指定 {m,n} は伝統的AWKでは動かず、GNU Awkではデフォルト有効ですが古いスクリプトとの互換のため--re-intervalオプションを必要とするケースもあります。移植性のため、できるだけシンプルな正規表現か、文字クラス [[:digit:]]等を用いるようにしましょう。また、\w\dといったPerl風のエスケープシーケンスはAWKの正規表現では意味を持たないことにも注意が必要です(代わりに [A-Za-z0-9_][[:alnum:]_] を使います)。

  • パイプとサブシェル: AWKスクリプト内から外部コマンドを呼ぶ際はsystem()関数やパイプ出力/入力を使えます。ただし、プロセス上限やパフォーマンスに注意しましょう。例えば大量の行に対してprint | "grep something"のようにすると行ごとにgrepプロセスを起動してしまい非常に非効率です(この場合はAWK内で条件判定するか、一度全データをコマンドに渡すような構造にするべきです)。また、繰り返しprint > "file"する場合も都度オープン/クローズするとオーバーヘッドが大きくなるため、可能なら一度開いたままにし(AWKは自動でオープンしたファイルを管理します)、最後にまとめて閉じる方が良いでしょう。close()関数を適切に使うことも重要です。

  • in-place編集ができない: sedには-iオプション(その場編集)があるためAWKにも期待しがちですが、標準AWKにはファイルを直接書き換える-iオプションはありません。awk input.txt > output.txtのように出力を別ファイルにリダイレクトし、処理後にリネームする方法を取る必要があります。GNU Awkでは拡張ライブラリを利用して-i inplaceのような指定で擬似的なインプレース編集を行うこともできますが (stackoverflow.com)、挙動を十分理解して使う必要があります。[Non-POSIX][GNU]

  • バージョン差異: AWKスクリプトがある環境で動いても、別環境では動かないケースがあります。例えばBusyBox版のawkでは機能が限定されていたり、古いawkではENVIRONasort()が使えなかったりします。また、同じgawkでもメジャーバージョンアップで仕様が変わる場合があります(例: gawk 5で導入された真正の多次元配列は古い環境には存在しません)。スクリプトを配布する際は、なるべくサードパーティの拡張に頼らないか、冒頭でawk 'BEGIN { if (ARGV[1] == "--test-feature") ... }'のようにバージョンチェックを入れるなどの工夫も考えられます。

以上の点に留意しつつ、AWKを活用すると強力なテキスト処理がシンプルに実現できます。

使用例(例題)

ここでは、AWKコマンドの具体的な使用例をいくつか紹介します。基本的なものから高度なものまで、上記で説明した機能を実際に使った例を示します。

例1: 単純なフィールド抽出
テキストファイルdata.txtの各行について、2番目のフィールドを表示します。


# data.txt の例:

# Alice  300  A

# Bob    150  B

$ awk '{ print $2 }' data.txt
300
150

上記ではシンプルに$2printすることで各行の2番目のフィールド(ここでは数値部分)だけを出力しています。区切りが空白なのでデフォルトFSのままでOKです。

例2: 条件によるフィルタリング
ログファイルweb.logから、ステータスコードが400以上の行を抽出します。ステータスコードは第9フィールドにあるとします(common log formatなどを想定)。

$ awk '$9 >= 400' web.log

このAWKワンライナーは、パターンに$9 >= 400という条件式を使い、9番目のフィールドが400以上の行のみをデフォルトアクション(print $0)で表示します。つまりクライアントエラーやサーバエラーのログ行だけが出力されます。

例3: フィールドの並べ替え
ファイルnames.txtには「名字,名前,年齢」という順序でカンマ区切りのデータが入っています。これを「名前(スペース)名字:年齢」の形式で出力します。


# names.txt の例:  Yamada,Taro,28

$ awk -F, '{ print $2 " " $1 ":" $3 }' names.txt
Taro Yamada:28

-F,でカンマ区切りを指定し、$1(名字), $2(名前), $3(年齢)を適宜並べ替えて表示しています。文字列連結とスペース/コロンのリテラルを組み合わせ、指定のフォーマットを実現しています。

例4: BEGINブロックでヘッダー出力
システムコマンドdf -hの出力からマウントポイントと使用率を抽出し、見出し行を付けて整形します。

$ df -h | awk 'BEGIN { print "Mount\tUsed" }
               NR>1 { print $6 "\t" $5 }'
Mount   Used
/       20%
/home   55%
…(以下略)

df -hの結果をパイプでAWKに渡し、BEGINでヘッダ行を、NR>1で1行目(ヘッダ)以外を処理しています。使用状況の列(5列目)とマウントポイントの列(6列目)を抽出し、タブ区切りで表示しました。

例5: 配列を使った集計
テキストsales.txtに「店舗 商品 金額」というデータが複数行あるとします。店舗ごとの売上合計を算出します。


# sales.txt の例:

# Tokyo  Apple   500

# Osaka  Banana  200

# Tokyo  Orange  300

# Osaka  Apple   150

$ awk '{ store=$1; amount=$3; total[store] += amount }
       END { for(s in total) print s, total[s] }' sales.txt
Osaka 350
Tokyo 800

各行について、配列totalのキーを店舗名、値を金額累計として加算しています。ENDブロックで各店舗の合計を出力しました。このようにキーごとの集計はAWKの得意分野です。

例6: 複数ファイルの処理
2つのファイルfileA.txtfileB.txtを順番に処理し、各行を「ファイル名:行番号:内容」の形式で出力します(前述したFILENAMEとFNRの利用例です)。

$ awk '{ print FILENAME ":" FNR ": " $0 }' fileA.txt fileB.txt
fileA.txt:1: (fileAの1行目の内容)
fileA.txt:2: (fileAの2行目の内容)
... 
fileB.txt:1: (fileBの1行目の内容)
fileB.txt:2: (fileBの2行目の内容)
... 

それぞれの行に対して、どのファイルの何行目かが付加された形で出力されます。このワンライナーからも分かるように、複数ファイルを渡した場合AWKは自動的にファイルを切り替えて通しで処理してくれます。

例7: テキスト置換と書式変換
テキストinput.txtの内容を読み込み、特定の文字を別の文字に置換しつつ、行頭に行番号を付けて新しいファイルに保存します。例えば全てのタブをカンマに置換し、"output.txt"に出力するケースです。

$ awk '{ gsub(/\t/, ","); print NR ": " $0 > "output.txt" }' input.txt

このAWKスクリプトは入力を1行ずつ処理し、各行でまずgsub(/\t/, ",")により全てのタブ文字をカンマに置換しています。続けてprint NR ": " $0 > "output.txt"で、変換後の行を行番号付きで"output.txt"ファイルに書き出しています。出力リダイレクトによって、処理結果は端末ではなくファイルに蓄積されます(必要があれば処理後にclose("output.txt")で明示的にファイルを閉じることもできます)。

例8: 外部コマンドとの連携
AWKから外部コマンドを呼び出す例です。システム上のユーザ一覧をgetent passwdで取得し、それをAWKで解析してユーザ名のみをアルファベット順に表示してみます。

$ getent passwd | awk -F: '{ print $1 | "sort" }'

getent passwd/etc/passwdの内容(ユーザ情報)を出力するコマンドです。各行はコロン区切りで、1フィールド目がユーザ名です。このAWKではフィールド区切りをコロンにし、$1(ユーザ名)を外部コマンドsortにパイプで渡しています。AWKは全行についてユーザ名をsortプロセスに送り、sortコマンドはその一覧をソートして最終結果を出力します。注意: このようにパイプ出力を使う場合、AWK側でclose("sort")を適切なタイミングで呼ばないと、sortへの入力が終わったことが伝わらずソート結果が表示されないことがあります。今回のケースではAWK終了時に自動的にパイプは閉じられるため結果が表示されますが、大量データの場合は明示的にEND{ close("sort") }を入れるのが確実です。

練習問題

最後に、AWKの理解を深めるための演習問題を示します。それぞれ実際に手を動かして試し、解答例と照らし合わせてみてください。難易度は初級から上級まで混在しています。

問題1: ファイルnumbers.txtには1列に整数が並んでいます(1行に1つの整数)。この全ての数の合計をAWKで計算して表示してください。

<details><summary>解答を見る</summary><div>

解答: awk '{ sum += $1 } END { print sum }' numbers.txt

各行の数値をsumに加算し、ENDブロックで合計を出力しています。初期値は0で暗黙的に初期化されており、空ファイルの場合は何も出力されません(0と出したい場合はBEGINで初期化と出力処理を工夫します)。

</div></details>

問題2: CSV形式のファイルdata.csvがあります。各行は「名前,年齢,都市」という項目になっています。このファイルから名前と年齢の列だけを抜き出し、スペース区切りで表示するAWKコマンドを書いてください。

<details><summary>解答を見る</summary><div>

解答: awk -F, '{ print $1, $2 }' data.csv

-F,でカンマ区切りを指定し、1列目(名前)と2列目(年齢)を出力します。例えば入力がAlice,30,TokyoであればAlice 30と表示されます。

</div></details>

問題3: ログファイルaccess.logから、"ERROR"という単語を含む行だけをすべて抜き出して表示してください(大文字小文字は区別しないものとします)。AWKを使った一例を示しなさい。

<details><summary>解答を見る</summary><div>

解答: awk '/[Ee][Rr][Rr][Oo][Rr]/' access.log

正規表現/[Ee][Rr][Rr][Oo][Rr]/は大文字小文字を区別せず"error"をマッチさせるテクニックです。このパターンだけを指定しているため、該当行がそのまま出力されます。GNU Awk限定ですが、BEGIN{ IGNORECASE=1 } /error/とすれば同様に大小区別なくマッチできます。どちらの方法でも目的は達成できます。

</div></details>

問題4: 2つのファイルfile1.txtfile2.txtをAWKで同時に処理し、各行の先頭に「<ファイル名>:<行番号>: 」というプレフィックスを付けて出力したいとします。これを実現するAWKワンライナーを考えてください。

<details><summary>解答を見る</summary><div>

解答: awk '{ print FILENAME ":" FNR ": " $0 }' file1.txt file2.txt

AWKに複数ファイルを渡すと順次処理します。組み込み変数FILENAMEで現在のファイル名、FNRでそのファイル内の行番号を取得できるので、それらと行内容$0を組み合わせて所定の形式で出力しています。

</div></details>

問題5: scores.txtには「名前 点数1 点数2 点数3 ...」というフォーマットの成績データが入っています(科目数は行によって同じとします)。各行について名前と平均点を計算して表示するAWKスクリプトを書いてください。平均は小数点第一位まで表示すること。

<details><summary>解答を見る</summary><div>

解答: 次のようなAWKプログラムで実現できます。

awk '{
  total = 0
  # 2列目以降を合計
  for(i = 2; i <= NF; i++) {
    total += $i
  }
  avg = total / (NF - 1)
  # 名前と平均(小数点1桁)を表示
  printf "%s %.1f\n", $1, avg
}' scores.txt

各行で2列目から最後の列までをループで足し合わせ、avgに平均値を計算しています。NF-1が科目数(名前以外のフィールド数)です。最後にprintfで名前と平均を小数点1桁にフォーマットして出力しています。

</div></details>

これらの演習問題を通じて、AWKの基本操作から応用まで一通り体験できたかと思います。AWKは強力ですが、実際に使って試行錯誤することで習熟できますので、ぜひ様々なテキスト処理に挑戦してみてください。

o3 Deep Research·today at 2:18 am