【 awk 】コマンド(応用編)――テキストの加工とパターン処理、制御構文Linux基本コマンドTips(119)

本連載は、Linuxのコマンドについて、基本書式からオプション、具体的な実行例までを紹介していきます。今回は、テキストのパターン処理を行う「awk(gawk)」コマンドです。

» 2017年06月16日 05時00分 公開
[西村めぐみ@IT]
「Linux基本コマンドTips」のインデックス

Linux基本コマンドTips一覧

 本連載では、Linuxの基本的なコマンドについて、基本的な書式からオプション、具体的な実行例までを分かりやすく紹介していきます。今回は、テキストのパターン処理を行う「awk(gawk)」コマンドです。連載第115回第116回第117回第118回に続き、awkの応用を説明します。

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コマンドの書式

awk [オプション] [コマンド] [ファイル……]

※[ ]は省略可能な引数を示しています





awkの主なオプション

短いオプション 意味
-f ファイル名 awkスクリプトが書かれたファイルを指定する
-F 区切り文字 区切り文字を指定する(デフォルトは空白文字)
-v 変数名=値 変数を定義する

※gawk(GNU版awk)の場合、長いオプションも使用可能。-fは--file program-file、-Fは--field-separator、-vは--assign。gawkにはこの他にも多数のオプションがある。



awkで使用できる主な組み込み変数

変数名 意味
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 画面1 指定した文字列を含む行を行番号とともに出力したところ

 画面1では、アクション部分で「print NR, $0」だけを実行しています。この部分を拡張して、さらに複雑な処理を行うこともできます。この時使用するのが「制御構文」です。

 例えば条件に合っている時だけ実行したり、繰り返しの処理を実行したりします。パターンだけでは条件を指定できない、あるいは、1行単位の処理だけでは不十分な時に制御構文を使います。

awkで使用できる主な制御構文

制御構文 意味
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


画面2 画面2 条件文ifを用いたスクリプトを実行したところ

 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>


「++count」と「count++」の違い

 「++」は「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


画面3 画面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.

RSSについて

アイティメディアIDについて

メールマガジン登録

@ITのメールマガジンは、 もちろん、すべて無料です。ぜひメールマガジンをご購読ください。