本連載は、Linuxのコマンドについて、基本書式からオプション、具体的な実行例までを紹介していきます。今回は、テキストのパターン処理を行う「awk(gawk)」コマンドです。
本連載では、Linuxの基本的なコマンドについて、基本的な書式からオプション、具体的な実行例までを分かりやすく紹介していきます。今回は、テキストのパターン処理を行う「awk(gawk)」コマンドです。連載第115回、第116回、第117回、第118回に続き、awkの応用を説明します。
「awk」は空白などで区切られたテキストを処理するコマンドです。演算機能もあり、プログラミング言語としても使用されています。
Linux環境で使用されているのは、GNUプロジェクトによる「gawk」コマンドが多く、例えばCentOS 7の場合、awkは/usr/bin/gawkへのシンボリックリンクとなっています。
Ubuntu 15では、Michael D. Brennan氏による「mawk」が収録されています(awkは/etc/alternatives/awkへの、/etc/alternatives/awkは/usr/bin/mawkへのシンボリックリンク)。
どちらも、もともとのawkに加えてPOSIX 1003.2への準拠や組み込み変数、正規表現指定のバリエーションなどが拡張されています。
awk [オプション] [コマンド] [ファイル……]
※[ ]は省略可能な引数を示しています
| 短いオプション | 意味 |
|---|---|
| -f ファイル名 | awkスクリプトが書かれたファイルを指定する |
| -F 区切り文字 | 区切り文字を指定する(デフォルトは空白文字) |
| -v 変数名=値 | 変数を定義する |
※gawk(GNU版awk)の場合、長いオプションも使用可能。-fは--file program-file、-Fは--field-separator、-vは--assign。gawkにはこの他にも多数のオプションがある。
| 変数名 | 意味 |
|---|---|
| ARGC | 引数の個数 |
| ARGV | 引数(配列) |
| ENVIRON | 環境変数を収めた連想配列。例えば環境変数LANGならばENVIRON["LANG"]と参照できる |
| FILENAME | 現在処理しているファイルの名前 |
| FNR | 現在処理しているファイルのレコード番号(処理しているファイルが1つの場合はNRと同じ値になる) |
| FS | フィールドの区切り文字(-Fオプションで変更可能、デフォルトはスペース) |
| NR | 現在処理しているレコード番号(行番号) |
| OFS | 出力時のフィールドの区切り(デフォルトは空白) |
| ORS | 出力時のレコードの区切り(デフォルトは改行) |
| RS | レコードの区切り(デフォルトは改行) |
awkコマンドでは処理内容を「パターン {アクション}」と指定します。パターンに合致したらアクションを実行する、という意味です。例えば「awk '/文字列/ { print NR, $0 }' ファイル名」であれば、ファイルのうち、指定した文字列が書かれている行だけを行番号付きで出力する、という処理になります(画面1)。
「print NR, $0」の「NR」は行番号を表す組み込み変数で、「$0」は読み込んだ行全体、という意味です。桁数を指定したい場合はprintfを使用します(連載第117回、第118回)。
awk '/文字列/ { print NR, $0 }' ファイル名
(文字列が含まれる行だけを行番号付きで出力する)
awk '/bash/ { print NR, $0 }' /etc/shells(画面1)
(/etc/shellsでbashが含まれる行だけを行番号付きで出力する)
実行例ではawkのアクション部分で「{}」記号前後の空白を省略して入力しています。
画面1では、アクション部分で「print NR, $0」だけを実行しています。この部分を拡張して、さらに複雑な処理を行うこともできます。この時使用するのが「制御構文」です。
例えば条件に合っている時だけ実行したり、繰り返しの処理を実行したりします。パターンだけでは条件を指定できない、あるいは、1行単位の処理だけでは不十分な時に制御構文を使います。
| 制御構文 | 意味 |
|---|---|
| if (条件) 処理 | 条件に合っているときだけ処理を実行する(本文参照) |
| if (条件) 処理1 else 処理2 | 条件に合っている時は処理1を、それ以外の時は処理2を実行する |
| switch 〜 case 〜 | 複数の条件によって処理を分岐させる |
| while (条件) 処理 | 条件が成立している間は処理を実行する |
| do 処理 while (条件) | 条件が成立している間は処理を実行する。先に処理を済ませてから条件をチェックするため、必ず1回実行される |
| for (開始処理; 増減処理; 終了条件) 処理 | 繰り返し実行。「for (i=0; i++; i<10) 処理」で、変数iを0、1、2……9と1ずつ増やしながら処理を実行する。()の中は最初にiを0にする、iを1ずつ増やす、iが10より小さい間、という意味 |
| for (変数名 in 配列名) 処理 | 繰り返し処理。配列の内容を順番に変数にセットして処理を実行する |
| break | 処理を中断して繰り返し処理から抜ける |
| continue | 処理を中断して繰り返し処理の最初に戻る |
| delete 配列名 | 配列を削除する。delete 配列名[1]のように位置を指定することも可能 |
| exit 戻り値 | スクリプト全体を終了する。戻り値は省略可能 |
※複数の処理を書きたい場合は 「;」で区切って1行で書くか、「{〜}」で囲む。
簡単な条件文を書いてみましょう。次に示したのは、HTMLファイルから、部分だけを出力するという処理を行うスクリプト(script.awk)です。スクリプト内の「#」以降はコメントです。
BEGIN{
flag=0 #flagを0にしておく
count=0 #countを0にしておく
}
/<script/{
print "===script("++count")===" #===script(番号)===を出力
flag=1 #<scriptが含まれていたらflagを1にする
}
/<\/script/{
print #</scriptが含まれていたらその行を出力する
flag=0 #flagを0にする
}
{
if (flag==1){
print #flagが1の時は出力する
}
}
このスクリプトは4つのブロックからできています。「BEGIN{〜}」は全ての処理に先だって1回だけ実行されます(連載第117回)。ここでは「flag」という変数と「count」という変数に「0」をセットしています。flagはスクリプトの中で出力するかどうかを判定するために、countは部分の個数を数えるために使用しています。
2番目のブロック「/<script/{〜}」では、「<script」が含まれる行を見つけたら「===script(番号)===」という行を出力してflagを「1」にセットしています。countの処理については後述します。パターンを「/<script>/」としていないのは、HTMLでは「<script src="〜">」というような指定があるためです。
次の「/<\/script/{〜}」では、「</script」が含まれる行を見つけたら、その行の内容を出力して、flagを0にしています。
最後のブロックではパターンが指定されていません。従って、全ての行でアクション部分が実行されます。「if(flag==1){ print }」という処理は、flagが1ならばprintを実行するという意味です。
このスクリプトを「script.awk」として保存し、リストに挙げた「test.html」というファイルに対して実行すると以下のようになります(画面2)。
awk -f script.awk test.html(画面2)
test.htmlの内容は以下の通りです。
<html>
<head>
<title>SAMPLE</title>
<script src=""><!-- dummy --></script>
</head>
<body>
<h1>sample html</h1>
<script type="text/javascript">
document.write("<p>");
document.write("Hello World!");
document.write("</p>");
</script>
</body>
</html>
「++」は「1増やす」という意味の演算子です。同じように「--」で「1減らす」ことができます。
さらに、awkでは「count++」と「++count」という書き分けができます。単体で実行する場合はどちらの意味も同じですが、今回の「print ++count」のように、何かの処理の中で書く場合は、意味が異なります。
「print ++count」の場合は、「countを1増やしてからprint」、「print count++」の場合は「printしてからcountを1増やす」という意味です。
以下のスクリプトを使って試してみましょう。iとjは行をカウントしている変数というイメージです。
BEGIN {
i=0
j=0
}
{
print i++,++j,$0
}
END {
print i,j
}
処理内容が短いので、実行例ではスクリプトファイルを別に作成せず、コマンドラインで直接実行しています。「/etc/shellsにiとjで行番号を付けて出力し、最後に行数としてiとjを出力」という処理です(画面3)。iとjの値が処理中に1つずれていることが分かります。
awk 'BEGIN{i=0; j=0} {print i++,++j,$0} END{print i,j}' /etc/shells(画面3)
なお、先ほどのscript.awkにあった「/<script/{〜}」部分では「++」を使っていました。「++」を使わずに書き換えると以下のようになります。
#++を使った場合
/<script/{
print "===script("++count")==="
flag=1
}
#++を使わない場合
/<script/{
count = count + 1
print "===script("count")==="
flag=1
}
西村 めぐみ(にしむら めぐみ)
PC-9801NからのDOSユーザー。PC-486DX時代にDOS版UNIX-like toolsを経てLinuxへ。1992年より生産管理のパッケージソフトウェアの開発およびサポート業務を担当。著書に『図解でわかるLinux』『らぶらぶLinuxシリーズ』『はじめてでもわかるSQLとデータ設計』『シェルの基本テクニック』など。2011年より、地方自治体の在宅就業支援事業にてPC基礎およびMicrosoft Office関連の教材作成およびeラーニング指導を担当。
Copyright © ITmedia, Inc. All Rights Reserved.