awkスクリプトの備忘録メモです。
環境
- mac OS
- Bash
- awk
- iTerm2
awkの動き
ターミナルからコマンドを入力して試してみます。
echo
$ echo -e "001 aaa\n002 bbb\n003 ccc"
-e
オプションで文字列中にエスケープシーケンスが使えるようになる。
1行ごとに処理する
awkの基本の動作。
{ action }
action部で標準入力から渡されたテキストを1行づつ処理します。
$ echo -e "001 aaa\n002 bbb\n003 ccc" | awk '{ print }'
001 aaa
002 bbb
003 ccc
条件に合った入力だけ受け入れる
awk 'pattern { action }'
を使って、actionに渡す前にpattern
部に条件を指定して条件に合ったテキストだけをaction
部に渡すことができます。
$ echo -e "001 aaa\n002 bbb\n003 ccc" | awk '$1 ~ /"001"/ { print }'
Kazu-MacBook-Pro:awk-sample kazoo$ echo -e "001 aaa\n002 bbb\n003 ccc" | awk '$1 ~ /001/ { print }'
001 aaa
Kazu-MacBook-Pro:awk-sample kazoo$ echo -e "001 aaa\n002 bbb\n003 ccc" | awk '$1 ~ /(001)/ { print }'
001 aaa
Kazu-MacBook-Pro:awk-sample kazoo$ echo -e "001 aaa\n002 bbb\n003 ccc" | awk '$1 ~ /(001|002)/ { print }'
001 aaa
002 bbb
例えば、空の行がある場合に、パターン部に正規表現を利用して空行以外を処理できます。
$ echo -e "001 aaa\n002 bbb\n\n003 ccc" | awk '{ print }'
001 aaa
002 bbb
003 ccc
Kazu-MacBook-Pro:awk-sample kazoo$ echo -e "001 aaa\n002 bbb\n\n003 ccc" | awk '/^$/ { print }'
Kazu-MacBook-Pro:awk-sample kazoo$ echo -e "001 aaa\n002 bbb\n\n003 ccc" | awk '/[^$]/ { print }'
001 aaa
002 bbb
003 ccc
BEGIN
BEGINパターンを利用して前処理ができる。
例えば、RS(入力レコードセパレーター)を標準の改行から空白に設定する。
$ echo -e "001,aaa 002,bbb 003,ccc" | awk 'BEGIN{ RS = " " }{ print }'
001,aaa
002,bbb
003,ccc
組み込み変数
awk言語にもシステムで定義された特別な変数があります。
FS
フィールドセパレーター。入力行のフィールド区切り文字が設定されています。
デフォルトは空白。
RS
レコードセパレーター。入力行のレコード区切り文字が設定されています。
デフォルトは改行文字。
NR
1から始まるカレントレコードの番号が設定されています。
NF
カレントレコードのフィールド数が設定されています。
$ echo -e "001,aaa 002,bbb 003,ccc" | awk 'BEGIN{ RS = " " }{ print NR, NF, $0 }'
1 1 001,aaa
2 1 002,bbb
3 0
4 1 003,ccc
$ echo -e "001,aaa 002,bbb 003,ccc" | awk 'BEGIN{ RS = " " }{ if(NF != 0){ print NR, NF, $0 } }'
1 1 001,aaa
2 1 002,bbb
4 1 003,ccc
bashの中でawkを利用する
bashの中でawkを利用する場合の書き方です。
読み込みファイルはShift-JISで用意されている前提
Id,IsDelete,ParentId,Name
"00001",0,"000001","サンプルファイル1.txt"
"00002",0,"000001","サンプルファイル2.txt"
"00003",0,"000002","サンプルファイル3.txt"
スクリプトは下記の感じ
# ! /bin/sh
cat t1-sjis.csv | \
nkf -Sw | \
awk -F, \
'{
if (NR != 1) {
system("if test ! -d "$3"; then mkdir "$3"; fi")
system("if test -d "$3"; then cp "$1" ./"$3"/"$4"; fi")
print NR
print $1,$3,$4
}
}'
説明
-
nkf -Sw
nkfでShift-jisをutf-8に変換 -
NR != 1
1行目のヘッダーは処理しない -
system("if test ! -d "$3"; then mkdir "$3"; fi")
ディレクトリが無かったら作る
変数と配列
- awkの配列とハッシュには違いが無いので、文字列をキーにセットすれば配列はハッシュになる。
- ハッシュの取り出しは
for (value in array) array[value]
とする。
# ! /bin/sh
cat t1-sjis.csv | \
nkf -Sw | \
awk -F, \
'{
array[$3] = $4
print array[$3]
print $1,$3,$4
value01="Hello"
}
END {
for ( item in array)
print item, array[item]
print value01
}
'
$ ./awk-sample02.sh
Name
Id ParentId Name
"サンプルファイル1.txt"
"00001" "000001" "サンプルファイル1.txt"
"サンプルファイル2.txt"
"00002" "000001" "サンプルファイル2.txt"
"サンプルファイル3.txt"
"00003" "000002" "サンプルファイル3.txt"
ParentId Name
"000002" "サンプルファイル3.txt"
"000001" "サンプルファイル2.txt"
Hello
シェルの変数をawkに渡す
$2はシェルのコマンドパラメータ
awk の -vオプションを使ってawkで利用する変数にシェルの変数をセットする
awk -v attachments_dir="$2" -F, \
'
BEGIN {
print attachments_dir
}
'
2つ以上の変数を渡すとき
awk -v file_name="$1" -v attachments_dir="$2" -F, \
'
BEGIN {
print file_name attachments_dir
}
'
ソート
パイプでソートする
パイプでsortコマンドに渡すことでソート処理を行う
for ( item in array)
print item, array[item] | "sort"
"000001" "サンプルファイル2.txt"
"000002" "サンプルファイル3.txt"
ParentId Name
ループ
whileループ
while ("ls ./"attachments_dir";" | getline) {
ls_out[$0] = ++i
}
forループ
※ls_outは連想配列
for (i in ls_out) {
print i, ls_out[i]
}
ファイル
ファイルオープン・クローズ
print文をリダイレクトするとファイルが作成される。
">>"を使うと既存ファイルに追記できる。
# !/bin/bash
awk \
'
BEGIN {
# file open
print > "sample1.csv"
close("sample1.csv")
}
'
ファイル出力
# !/bin/bash
awk \
'
BEGIN {
# file open
print "Hello awk" > "sample1.txt"
# file close
close("sample1.txt")
}
'
ファイル入力
# !/bin/bash
awk \
'
{
print $0
}
'
$ cat sample.csv | ./awk-sample003.sh
コマンドラインパラメータの利用
※sample.csv は読み込み対象のファイル
test は if [ ! -f "$1" ]; then
でも同じ
# !/bin/bash
if test ! -f "$1"; then
echo "$1"'ファイルが存在しません'
exit 1
fi
cat "$1" | \
awk \
'
{
print $0
}
'
exit 0
$ ./awk-sample003.sh
ファイルが存在しません
$ ./awk-sample003.sh sample.csv
文字列処理
文字列を分割する
split()
split("文字列", "配列", "分割文字")
戻り値:配列の要素数
配列の添字は1から始まるので注意。
# !/bin/bash
# if test ! -f "$1"; then
if [ ! -f "$1" ]; then
echo "$1"'ファイルが存在しません'
exit 1
fi
cat "$1" | \
nkf -Sw | \
awk -F, \
'
{
split($2, name, " ")
print $1 "," $2 "," name[1] "\"" "," "\""name[2]
}
'
exit 0
※sample01.csvの中身は、cybozu kintoneのアプリストア「従業員名簿アプリ」のサンプルデータをお借りしております。
$ cat sample01.csv | nkf -Sw | head -5
"従業員番号(必須)","氏名","雇用形態","入社日","生年月日","TEL","メールアドレス"
"001","富田 美波","正社員","2014/04/01","1992/01/14","0801234****","minami-toda@sample.sample"
"002","山本 健太","正社員","2015/05/29","1985/05/25","0908989****","yamamoto123@sample.sample"
"003","佐藤 昇","正社員","2017/05/29","1986/05/16","0001234****","sato@sample.sample"
"004","太田 隆","正社員","2018/05/29","1977/11/01","0705678****","ohta-takashi@sample.sample"
$ ./awk-sample005.sh sample01.csv | head -5
"従業員番号(必須)","氏名","氏名"","
"001","富田 美波","富田","美波"
"002","山本 健太","山本","健太"
"003","佐藤 昇","佐藤","昇"
"004","太田 隆","太田","隆"
文字列中の位置を知りたい
index()
index("文字列", "部分文字列")
$ awk 'BEGIN{print index("2021-03-26 00:00:00", " ")}'
11
文字列中の位置から部分文字列を取り出す
substr()
substr("文字列", 位置)
文字列から指定位置以降を取り出す
$ awk 'BEGIN{print substr("080-123-9999", 5)}'
123-9999
正規表現にマッチした位置を知りたい
match()
match("文字列", /正規表現/)
取り出しは RSTART, RLENGTH を利用する。
RSTART→マッチした文字の位置
RLENGTH→マッチした文字数
$ awk 'BEGIN{match("2021-03-26 00:00:00", / /); \
print RSTART, RLENGTH }'
11 1
$ echo "2021-03-26 00:00:00" | awk '{match($0, / /); \
print RSTART, RLENGTH; \
print substr($0, RSTART+1) \
}'
11 1
00:00:00
文字列置換
sedコマンドを使う
$ echo "2021-03-26 00:00:00" | sed 's/ /T/'
2021-03-26T00:00:00
$ echo "2021-03-26 00:00:00" | sed 's/\([1-2][0-9]\{3\}[-]\)\(.*\)/\2/'
03-26 00:00:00
$ echo "2021-03-26 00:00:00" | sed 's/\([1-2][0-9]\{3\}[-][01][1-9][-][0-3][0-9]\)\(.*\)/\2/'
00:00:00
$ echo "2021-03-26 00:00:00" | sed 's/\([12][0-9]\{3\}[-][01][1-9][-][0-3][0-9]\)\( \)\([0-2][0-9]:[0-5][0-9]\)\(.*\)/\1T\3Z/'
2021-03-26T00:00Z