AWK - 思い出していきます...
はじめに
awkは規定のフォーマットのテキストファイルを処理するのに便利なコマンドです。
awkの処理単位は改行で区切られた1行単位。
初めて使ったときは感動したことを覚えています。そのころAWKで結構いろいろ作ってました。
久々に見ると…orz
確認環境:WindowsのPowerShellでShellを動くようにしたタイミングでAWK,sedも動きました。
全レコード、レコードのフィールド指定で値を取得
まずは、簡単な例になります。
入力に対して必要な項目を出力するような感じです。
$ date | awk '{ print $0 }'
Tue Nov 15 23:36:15 JST 2022
$ date | awk '{ print $1 }'
Tue
$ date
Tue Nov 15 23:36:20 JST 2022
$ date | awk '{print $2 $3}'
Nov15
$ date | awk '{print $2, $3}'
Nov 15
$0 - レコード全体
$1 - 1番目のフィールド
$2 - 2番目のフィールド
となります。
任意のレコードを抽出
第1フィールドの値を指定して、合致しているレコードを取り出したい。
この結果をパイプで別の処理に投げるようにすればいいかな
$ cat list3.txt
key_aaaX aaa1 aaa2 aaa3
key_bbbX bbb1 bbb2
key_cccX ccc1
key_dddX ddd1 ddd2
key_eeeX eee1
key_aaaX fff1 fff2 fff3
$ awk '$1=="key_aaaX" {print $0}' ./list3.txt
key_aaaX aaa1 aaa2 aaa3
key_aaaX fff1 fff2 fff3
$ cat ./list3.txt | awk '$1=="key_aaaX" {print $0}'
key_aaaX aaa1 aaa2 aaa3
key_aaaX fff1 fff2 fff3
$ cat ./list3.txt | awk '$1=="key_bbbX" {print $0}'
key_bbbX bbb1 bbb2
ここで悩みました。
上記のコードをシェルにしたらいまいちな感じでした。どこかでAWKの動きを勘違いしているみたいです。
# FILENAME:01.sh
FILE_LIST=`awk '$1=="key_aaaX" {print $0}' < list3.txt`
for FNAME in ${FILE_LIST}
do
echo ${FNAME}
done1
1行単位で取り出したいのですが…空白区切りで取り出されます。
$ ./01.sh
key_aaaX
aaa1
aaa2
aaa3
key_aaaX
fff1
fff2
fff3
うーん…空白区切りで取り出しているし普通の動きにも思えてきた…。
うーん…AWKだけでできなかったっけ?
1行単位で処理する場合はどうするのか?
AWKだけでやっていた気がしたのですが、検索して調べていると…Shellで1行づつ取り出していたのかもしれません。
Shell側で1行づつ回す方法は…
<<ペンディング>>
ファイルの成形
データファイルをAWKに処理させます。
$cat ./list.txt
aaa 01 aaa_01
bbb 02 bbb_02
ccc 03 ccc_03
$awk '{ print $0 }' ./list.txt
aaa 01 aaa_01
bbb 02 bbb_02
ccc 03 ccc_03
$awk '{ print $1 }' ./list.txt
aaa
bbb
ccc
条件を記述できます。
2番目のフィールドが"02"の行を出力
$awk ' $2 == "02" { print $0 }' ./list.txt
bbb 02 bbb_02
この"02"を引数として与えることができます。
$ cat list.txt | awk -v v1="02" '$2 == v1 { print $2, $0 }'
02 bbb 02 bbb_02
複数の条件を指定したい場合は
以下はOR条件で抜き出しています。
$ cat list.txt | awk -v v1="02" '$2 == v1 || $2 == "03" { print $2, $0 }'
02 bbb 02 bbb_02
03 ccc 03 ccc_03
AND条件は…
$ cat list.txt | awk -v v1="02" '$2 == v1 || $1 == "bbb" { print $2, $0 }'
02 bbb 02 bbb_02
後は、正規表現で抜き出すような場合もあります。
<<ペンディング>>
ヘッダーの出力
最初に1回だけ処理したいような場合です。
-v オプション引数を与えることが出来ます。
BEGIN{}は最初に1回だけ実行します。
$cat list.txt | awk -v v1="Hello" -v v2="-" -v v3="World" 'BEGIN { print v1, v2, v3}{}'
Hello - World
$ cat list.txt | awk -v v1="name" -v v2="no" -v v3="name_no" 'BEGIN { print v1, v2, v3}{p
rint $0}'
name no name_no
aaa 01 aaa_01
bbb 02 bbb_02
ccc 03 ccc_03
フッターの出力
ヘッダーがあるならフッターを出力する方法もあります。
…いままで使ったことなかった気がします。
例は固定文字列ですが、-vオプションで引数を渡して表示することもできます。
$ cat list.txt | awk -v v1="name" -v v2="no" -v v3="name_no" 'BEGIN { print v1, v2, v3}{print $0} END{print "END"}'
name no name_no
aaa 01 aaa_01
bbb 02 bbb_02
ccc 03 ccc_03
END
注意)シェルと組み合わせる場合、引数が紛らわしい
シェルの引数も「$」使いますし、AWKの項目指定も「$」使うので、少しわかりにくくなってしまうので一旦、シェルの引数は別の変数に入れるのがよいと思います。
シェルプログラムで引数を扱うのは以下のようになります。
# FILENAME: 03.sh
# usage:./03.sh param1 param2
echo "START"
# 自身の名前は$0
echo $0
# 自身のファイル名のみ取り出す
echo `basename $0`
# 第1引数
echo $1
# 第2引数
echo $2
echo "END"
実行結果
$ ./03.sh AAA BBB
START
./03.sh
03.sh
AAA
BBB
END
このシェルにAWKを組み込んでみます。
やりたいことは、
シェルプログラムの第1引数に与えた文字列がlist.txtの第1フィールドに合致した場合、そのレコードの値を取り出す。
合致するレコードは1つのみを想定
コードは以下のようになるのですが…
パット見「$1に入れて$1を比較??」って…何してるのかわかりませんでした。
VAL=`cat list.txt | awk -v v1=$1 'v1 == $1 { print $0 }'`
よく見ると同じ「$1」でも
①「awk -v v1=$1」はシェル自体の引数、
②「v1 == $1 { print $0 }」は読み込んだレコードの第1フィールド
になっていることがわかります。
コード全体
# usage:./03.sh param1 param2
# echo "START"
# 自身の名前は$0
# echo $0
# 自身のファイル名のみ取り出す
# echo `basename $0`
# 第1引数
# echo $1
# 第2引数
# echo $2
VAL=`cat list.txt | awk -v v1=$1 'v1 == $1 { print $0 }'`
echo ${VAL}
# echo "END"
実行結果
$ ./03.sh bbb
bbb 02 bbb_02
$ ./03.sh ccc
ccc 03 ccc_03
$ ./03.sh aaa
aaa 01 aaa_01
この書き方もありがちだと思うのですが…個人的には一旦別変数にいれたほうが分かりやすいと思うのですが…
このあたりって、「この言語はこう書く」って定石もあるので何ともいえないんですけど…
各フィールドを個別で取り出したい
例えば、
・1レコードに何フィールドあるかわからないけど1つづつ取り出したい場合
・第1フィールドは固定、第2フィールド以降は1以上の複数の値を持つ場合
など…
1レコード取り出して処理する
<<後でまとめ、While使えばOK>>
<<<
後でまとめ、While使えば、以下のようなファイルを読み込んでも、フィールド毎にとりだせます。
data.txt
AAA BBB CCC
気になったのは...最低フィールド数で取り出さないと...想像していた動きとことなりました。
>>
$1,$2と指定しないでループで取得
<<次のタイミングで...記載>>