ここでは、_言語_のことをAWK、AWKプログラムを解釈する_実行ファイル_のことを**awkまたはgawk**と呼びます。
AWKプログラムはパターンとアクションの組で構成されます。
pattern1 { action1 }
pattern2 { action2 }
...
パターンには、入力と照合する正規表現パターンを指定できます。
trueかfalseになる任意の式でもかまいません。
アクションは、1つ以上のAWKコマンドです。 囲みの波括弧は必須です。
パターンの結果がtrueの場合、対応するアクションが評価されます。
AWKは入力を一連のレコードとして解析します。 各レコードは0個以上のフィールドに分割されます。
デフォルトの動作は次のとおりです。
パターンは省略できます。その場合、アクションは_すべての入力レコード_に対して評価されます。
アクションも省略できます。その場合のデフォルトの動作は、現在のレコードを出力することです。
よく使われる特別なパターンが2つあります。
BEGINは、入力が消費される前に対応するアクションを評価します。ENDは、すべての入力が消費された後に対応するアクションを評価します。フィールドは$1、$2、$3などで参照します。
現在のレコードは$0です。
ほかにも、よく使われる組み込み変数をいくつか紹介します。
RSは入力のレコード区切りです。
前述のとおり、デフォルト値は改行です。FSは入力のフィールド区切りです。これらの変数は、BEGINのアクションブロックで設定するのが一般的です。
これらの変数は、プログラムの実行中にAWKによって自動的に設定されます。
NFは、現在のレコードのフィールド数を保持します。
$NFを使います。NRは、現在のレコード番号を保持します。
NRはこれまでに処理したレコードの総数を保持します。FNRは、現在の入力ファイルにおける現在のレコード番号を保持します。
NR == FNR {action}
これは「蓄積されたレコード番号が、現在のファイルのレコード番号と等しい場合」という意味です。
これが成り立つのは、_最初の入力ファイル_のレコードだけです。AWKは、C言語と同じ真偽の考え方を使います。
0はfalseです。trueです。AWKはテキスト処理言語なので、真偽の値は文字列にも拡張されます。
""はfalseです。trueです。コメントは#で始まり、行の終わりまで続きます。
Unix系のシステムでは、/etc/passwdにユーザーに関する情報が含まれています。
各ユーザーは1行ずつ記述され、フィールドはコロンで区切られています。
ユーザー名を一覧表示するには、次のようにします。
awk '
BEGIN { FS = ":" }
{ print $1 }
' /etc/passwd
{print $1}というアクションにはパターンがないため、すべてのレコードに対して実行されることに注目してください。
ログインシェルとしてbashを使っているユーザーだけに出力を絞りたい場合は、次のようにします。
awk '
BEGIN { FS = ":" }
$7 == "/bin/bash" { print $1 }
' /etc/passwd
昔々、サイバースペースと呼ばれる賑やかな都市がありました。 サイバースペースはデジタルの大都市で、何百万人もの住民が毎日ログインしたりログアウトしたりしていました。 これらすべてのユーザーを管理するために、サイバースペースには/etc/passwdという特別なファイルがありました。
/etc/passwdファイルは、この都市の電話帳のようなものでした。 システムにアカウントを持つすべてのユーザーと、それぞれのユーザーに関するいくつかの重要な情報が記載されていました。
| n | フィールド |
|---|---|
| 1 | ユーザー名 |
| 2 | パスワード(「x」は、実際のパスワードがセキュリティのため別の場所に保存されていることを示します) |
| 3 | UID、一意の数値のユーザーID |
| 4 | GID、一意の数値のグループID(通常はUIDと同じです) |
| 5 | ユーザー情報(通常はユーザーのフルネームですが、他の情報を含むこともあります) |
| 6 | ホームディレクトリ |
| 7 | ログインシェル |
各フィールドはコロン文字で区切られています。 典型的なエントリーは次のとおりです。
glennj:x:1010:1010:Glenn Jackman:/home/glennj:/usr/local/bin/fish
このファイルはシステム管理者のチームによって管理され、新しいアカウントの作成、既存のアカウントの更新、古いアカウントの削除を担当していました。 システム管理者はサイバースペースの門番であり、許可されたユーザーだけがシステムにアクセスできるようにしていました。
ある日、アレックスという新しいシステム管理者がチームに加わるために雇われました。 アレックスはその仕事を引き受けることに胸を躍らせ、すぐに/etc/passwdファイルの仕組みを隅々まで学びました。 システムに新しいユーザーを追加する方法、ユーザー情報を更新する方法、古いユーザーを削除する方法を学びました。
アレックスが/etc/passwdファイルを扱っていると、何か奇妙なことに気づきました。 何人かのユーザーは、奇妙で変わったホームディレクトリを持っていました。 あるユーザーのホームディレクトリは/dev/nullに設定され、別のユーザーのホームディレクトリは存在しない奇妙なパスに設定されていました。
アレックスは、これらのユーザーが実在の人物ではなく、ハッカーによって作成された偽のアカウントであることに気づきました。 ハッカーはシステムの脆弱性を悪用する方法を見つけ、サイバースペースへのアクセスを得るためにこれらの偽アカウントを作成していました。
アレックスは、何か行動を起こす必要があると考えました。 アレックスはawkを使って/etc/passwdファイルを分析し、すべての偽アカウントを特定するスクリプトを書きました。 その後、チームの残りのメンバーと協力してこれらのアカウントを削除し、そもそもハッカーがそれらを作成できるようにしていた脆弱性にパッチを当てました。
アレックスがawkを使ったおかげで、チームは偽アカウントを特定して削除し、サイバースペースをみんなにとってより安全な場所にすることができました。 システム管理者たちは引き続き/etc/passwdファイルを管理し、常に最新かつ安全な状態に保ち、許可されたユーザーだけがシステムにアクセスできるようにしていました。
さあ、今度はアレックスのようなシステム管理者になる番です。
この演習を解くために必要になる、AWKの追加の構文をいくつか紹介します。
論理演算子は他の言語とよく似ています。
| 演算 | 例 |
|---|---|
| AND | expr1 && expr2 |
| OR | expr1 || expr2 |
| NOT | ! expr |
関数の呼び出しは、見慣れた構文になっています。
function greet(name) {
print "Hello", name
}
BEGIN {
greet("Mary") # prints "Hello Mary"
}
passwdファイルの各行について、コロンで区切られた最初のフィールドを出力するawkプログラムを書いてください。
入力ファイルの各レコードの行番号を出力するawkコマンドを書いてください。 行番号だけを出力し、それ以外の情報は出力しないでください。
サイバースペースでは、「有効な」ホームディレクトリは/homeまたは/rootで始まります。
入力ファイルのうち、無効なホームディレクトリを持つレコードを出力してください。
ホームディレクトリが有効で、かつログインシェルがbashであるレコードを出力してください。