シェルの組み込みスクリプト言語 AWK (オークと発音します)を使うと「あると便利」的なことが簡単にできます。
 

読み込んだファイルに行番号を付けて出力


 
ブログにコードを載せるときなんかに行番号付きで出力したいことがありますが、AWK を使えば1行のコマンドで実現できます。
 
行番号は AWK の組み込み変数「NR」に格納されいます。
行全体の内容は同じく組み込み変数の「$0」に自動的に格納されています。
 

 
$ awk '{print NR, $0}' file
 

 
例えば “hello, Shell!” と出力するだけの簡単なシェルスクリプト(ファイル名は「hello」)を行番号付きで表示させてみましょう。
 

 
$ awk '{print NR, $0}' hello
1 #!/bin/sh
2 #日付を表示して
3 date
4 #hello, Shell! と表示
5 echo "hello, Shell!"
 

 
ちなみに「{print NR, $0}」の部分が AWK の「アクション」と呼ばれる部分で、実際に出力作業を行なっている処理です。
AWK は自動的に読み込んだファイルの1行目から最終行に向かって指示されたアクションを繰り返し実行します。
もう一つ、「NR」のあとの , (コンマ)は、AWK に対して「この部分にスペースを入れなさい」という命令になってます。
 
 

パターンを指定してアクションを実行


 
AWK はただ単に全部の行にアクションを実行するだけでなく、実行する条件を「パターン」と呼ばれるテクニックで指定することができます。
 
例として、前述のファイル「hello」から # で始まるコメント行を飛ばして表示させてみましょう。
 

 
$ awk '/^[^#]/ {print NR, $0}' hello
3 date
5 echo "hello, Shell!"
 

 
アクション( { と } で囲まれた部分)の前に置いた「/^[^#]/」の部分がパターンになります。
行の先頭(^)が「#」で始まらない行([^#])を正規表現(/パターン/ の形式)で指定しています。
 
ただしこれだと先頭行で sh に処理をお願いしている部分(#!/bin/sh)も消えてしまいます。
先頭行は他のコメント行と違って # のあとに ! と続きます。
これを利用してパターンを改良してみましょう。
 

 
$ awk '/^(#!|[^#])/ {print NR, $0}' hello
1 #!/bin/sh
3 date
5 echo "hello, Shell!"
 

 
パターンを「/^(#!|[^#])/」と変更しました。
行の先頭(^)が「#!」で始まるか、または( | )、「#」で始まらない行([^#])にマッチするようになってます。
 
正規表現がややこしければ、単に1行目は表示してねというパターンにすることもできます。
 

 
$ awk 'NR == 1 || /^[^#]/ {print NR, $0}' hello
1 #!/bin/sh
3 date
5 echo "hello, Shell!"
 

 
AWK がファイルを読み始めると、最初の条件指定「NR == 1」で行番号(NR)が1行目かどうかを判定します。
「#!/bin/sh」は1行目に書いてありますので、行番号の判定は「true」となります。
 
その後に続く演算子が OR を意味する「||」になっているので、2番めに書いてある条件「/^[^#]/」は単に無視されて、処理はアクションへ移ります。
 
2行目以降は最初の条件指定「NR == 1」が常に「False」となって失敗するので、2番目の条件「/^[^#]/」で行の内容が評価されます。
 
 
さて、実はもうひとつ問題があります。
 
NR は、現在処理している「ファイルの中の行番号」を取ってきてしまうので、上記のやり方だとコメント行を飛ばした「1, 3, 5」という行番号になってしまいます。
 
ブログなどに載せる場合にはやはりここは連番にしたいものです。
 
解決方法としては、何か適当な変数を使って print アクションをする前に +1 ずつインクリメントさせるのが良いでしょう。
 
例えばインクリメントさせる変数を line とします。
 

 
$ awk 'NR == 1 || /^[^#]/ {line = line + 1; print line, $0}' hello
1 #!/bin/sh
2 date
3 echo "hello, Shell!"
 

 
「line = line + 1」という処理を print の前に置いてやることによって、パターンに指定した条件をクリアした時にだけ変数を1ずつインクリメントし、それを表示用の行番号として使うテクニックです。
 
この時、アクションの中には「line = line + 1」と「print line, $0」の2つの命令文が並んでいますので、文の終わりを明確にするために「line = line + 1」の文末には「;」を付けてあげる必要があります。日本語で言えば「。」に相当します。
 
 
 

printf を使って桁数指定


 
行数の多いファイルの場合、先頭に付ける行番号の桁が増えていくと、その分だけ右にずれてレイアウトが崩れてしまいます。
 
例えば、行番号を付けて表示させたいファイルの行数を数えてみたら2桁あったとします。
ちなみに AWK で行数を表示させるには次のように書きます。
 

 
$ awk 'END{print NR}' file
 

 
アクションの前に「END」と書いてありますが、これは ENDブロックと言って、最後の行まで AWK が読み終わったら実行される処理を記述するブロックです。
この場合、行を読むたびに更新される変数「NR」の最後の値だけ欲しいので ENDブロックで print させてます。
 
なお、ファイルを読み込む前に何か処理を実行したい場合は BEGINブロック(BEGIN{Action…})を使います。
 
 
実例で見て行きましょう。
 

 
$ awk 'END{print NR}' make_blog
53
 

 
「make_blog」という、僕が実際にブログを書いて整形する際に使ってる Perlスクリプトの行数を数えてみたら53行ありました。
 
このファイルの中身を前半で説明したやり方で行番号付きの表示にすると、行数が2桁になるところを境目にスペース1個分右にずれてしまいます。
 

 
$ awk '{print NR, $0}' make_blog
1 #!/usr/bin/perl
2 use warnings;
3 use strict;
4 
5 open OUT, ">", "result";
6 my $pre = 0;
7 my $icon = 0;
8 my $more = 0;
9 my $str = q{};
10 
11 print "\n" x 5;
12 
13 while(<>){
14	chomp;
15	
.
.
.
 

 
ね。
 
 
あるカラムの桁数を揃えて表示させたいときには printf を使います。
printf は「printf(書式, 値, 値, …)」の形式で使います。
 
行番号の表示桁数を2桁に揃えてみましょう。
 

 
$ awk '{printf("%2d %s\n", NR, $0)}' make_blog
 1 #!/usr/bin/perl
 2 use warnings;
 3 use strict;
 4 
 5 open OUT, ">", "result";
 6 my $pre = 0;
 7 my $icon = 0;
 8 my $more = 0;
 9 my $str = q{};
10 
11 print "\n" x 5;
12 
13 while(<>){
14 	chomp;
15 	
 .
 .
 .
 

 
ほらきれいに揃った。
 
printf の書式の中にある「%2d」の部分が「数字(d)を2桁で右揃えにしなさい」という命令になっています。
その後の「%s」は値を文字列(s)として表示するだけで、特に桁数は指定していません。
また最後に改行コード(\n)を付ける必要が(この場合)あるのも printf を使用する際の注意点です。
 
 
 
 

§1013 · Posted By · 12月 2, 2012 · Development · Tags: , , · [Print]