本連載は、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.