awk
基本文法
パターンに対応するアクションを行う
code:basic.awk
BEGIN { 前処理 }
パターン1 { アクション1 }
パターン2 { アクション2 }
...
END { 後処理 }
パターン=条件式
条件式の返り値の真偽値に基づいてアクションを実行する
table:真偽値
数字(≠文字列)の0 偽
空文字列 偽
それ以外 真
code:真偽値.sh
$ echo foo | awk '0 {print $1}'
(何も表示されない)
$ echo foo | awk '1 {print $1}'
foo
1オリジン
awkでは配列の要素数やレコードなどは1から始まる
区切り文字
区切り文字のデフォルトは空白かタブ文字
区切り文字の変更
-F で変更可能
-F, でカンマを区切り文字にする。
-F"[:,]" で複数指定可能
レコード
組み込み変数 RS(Record Separator) で区切られた行のこと
awkの処理単位
変数
フィールド変数
先頭が $
{print $1} で一個目のフィールドを出力
{print $0} は行全体
通常の変数
{a=5; print a;} 変数 a に 5 を代入して出力
{a=5; print $a;} フィールド変数$5と同じ
代入
$1 = "A" 第1フィールドを A に上書き
代入の返り値
左辺値が返り値となる
code:代入の返り値.sh
$ echo "a b c" | awk '{print $2 = "B"}'
B
$ echo "a b c" | awk '{print $2 = ""}'
(何も表示されない)
組み込み変数
table:組み込み変数
変数名 説明 サンプル 意味
FS セパレータ指定(-F と同じ) FS="\t" タブ文字を区切り文字に指定 NF フィールド数 { print $NF } 最後のフィールドをプリント
NR 行数 END { print $NR } 処理した行数を表示
RS 行の区切り文字(デフォルトは改行文字"\n")
ORS print時の改行文字(デフォルトは改行文字"\n")
FILENAME 処理中のファイル名
OFMT 数値の出力形式
OFS print関数での区切り(OutputFieldSeparator) print $1, $2 print内の","の置き換え(デフォルトでスペース)
length 行の文字総数
パターン指定
正規表現
awk '/正規表現/ { アクション }' (先頭に $0 ~ が暗黙的に付与される)
特定フィールドが正規表現に一致したもの
awk '$n ~ /正規表現/ { アクション }'
特定フィールドが正規表現に一致しないもの
awk '$n !~ /正規表現/ { アクション }'
空行を飛ばす
awk 'NF > 0 { アクション }'
$ cat foo.txt | awk 'NF' (フィールド数が0の行が偽となりスキップされる)
先頭10〜20行を抜き出す
$ seq 1 100 | awk 'NR >= 10 && NR <= 20'
末尾10行を抜き出す(tailもどき)
awkは先頭から順に読み込むため、素直に実装すると非効率
リングバッファ的なものを利用する
$ seq 1 100 | awk '{ a[NR % 10] = $0 } END { for(i = NR + 1; i <= NR + 10; i++) print a[i % 10]}
文字列の操作
結合
awk '{print "a" "b"}' 並べればOK
数値の文字列化
awk '{ print 0 "" }' 文字列と連接させて明示的に文字列として扱う
三項演算子
awk '{ ORS = NR % n ? "," : "\n"; print}'
ORS(printの末尾) をnで割った余りが0の時 \nが設定される
範囲演算子
範囲を指定する演算子
awk 'NR == 10, NR == 20' 10行目から20行目まで真になる
正確な動作
スイッチのような動作
NR == 10になったらパターンを真にしてNR == 20になったらパターンを偽にする(を繰り返す)
awk '真になるパターン, 偽になるパターン'
seq 1 10 | awk 'NR % 2 == 0, NR % 3 == 0' を実行すればわかる
処理のスキップ
next命令を使う
awk 'NF == 1 { next }' 最初の行を飛ばす
関数
awkの関数は一価関数
返り値は1つのみ
code:関数例.sh
$ echo "a b c" | awk '{ print(length) }' # length は () が省略できる特殊な関数(暗黙的に$0を見る)
5
table:関数
関数名 効果 返り値
sub(/正規表現/, "文字列") 文字列の置換(最初のみ) 成功:1/失敗0
gsub(/正規表現, "文字列") 文字列の置換(マッチしたところ全部) 成功した個数を数値
split("文字列", arr) 文字列の分割 分割個数
substr($N, m:int, n:int) m文字目からn文字抜き出す(n省略時は全部) 抜き出した文字列
index($N, "文字列") 文字列が最初に出現する箇所を数値で返す 最初に出現した位置
toupper("文字列") 大文字変換 変換した文字列
tolower("文字列") 小文字変換 変換した文字列
数値計算
値はすべて倍精度浮動小数点数として扱う
スニペット
code:再帰的リネーム.sh
$ find . -type f | while read FILE
do
echo ${FILE} | awk -F/ '{print "mv",$0, $1"/"$2"/"$3"/1."$4}' | sh
done
code:1フィールド目だけ表示するショートカット.sh
$ cat foo.txt | awk '$0 = $1'
code:何個マッチするか数える.sh
$ echo "ab ba ac ab" | awk '$0 = gsub(/ab/, "") ""' # 0個のときも表示したいので "" を連接させる
2
code:フィールドの再構築.sh
# csvのカンマ区切りをスペースに置き換える $1 = $1 はフィールドの再構築という手法
$ echo "0, 1, 2" | awk -F, -v OFS=' ' '$1 = $1'
code:正規表現のマッチ部分の抜き出し.sh
$ echo 'abcdef' | awk 'match($0, /b.*e/) { print substr($0, RSTART, RLENGTH) }'
bcde
# 実は grep でやるほうが簡単
$ echo 'abcdef' | grep -o 'b.*e'
bcde
# 横一列を縦一列に
$ echo 'abc' | grep -o '.'
a
b
c
code:標準入力を受け取る.sh
# 標準入力を使う。ちょっとした動作確認などに便利。
$ awk '{ アクション }' -
参考文献
Volodymyr Gubarkov
awkスペシャリストのブログ
AWKのオリジナル(公式?)実装
EBNF Grammar for AWK
Golang実装
AWKで実装されたファイルマネージャ
AWKでGitを実装する
awkでライフゲーム(リアルタイム処理) - utthi_fumiの日記
awkでRPGゲーム - utthi_fumiの日記
LRパーサー
CLIでGoogle翻訳を呼び出すawkスクリプト
IntelliJ-awk
frawk
Rust による awk 言語実装.主に高パフォーマンスと CSV 対応が売り.型推論によって全ての変数の型を静的に明らかにし SSA 形式の IR に変換 → LLVM で JIT コンパイルする.レコード単位での処理の並列実行をサポート AWKを始めとするコマンドラインテキスト処理についてのまとめ
awk with csv
Old school Awk
awk の基本的なデモスクリプト集
Modernizing AWK, a 45-year old language, by adding CSV support
Understanding AWK - Earthly Blog
The AWK State Machine Parser Pattern
Grep, sed and awk – The Right Tool For The Job
grep, sed, awk の使い分けの基本的指針
https://www.usp-lab.com/img/book.awk.s.jpg