hello
$ echo hello | awk '{print $0}'
hello
サンプルデータ (num.txt) の準備
$ awk 1 >num.txt
one eins yi 1 0
two zwei er 2 1
three drei san 3 1
four vier si 4 2
five funf wu 5 3
six sechs liu 6 5
seven sieben qi 7 8
eight acht ba 8 13
nine neun jiu 9 21
ten zehn shi 10 34
(Ctrl-d で入力終了)
$ awk 1 num.txt
one eins yi 1 0
two zwei er 2 1
three drei san 3 1
four vier si 4 2
five funf wu 5 3
six sechs liu 6 5
seven sieben qi 7 8
eight acht ba 8 13
nine neun jiu 9 21
ten zehn shi 10 34
基本
$2
で2列目が取り出せる
$ cat num.txt | awk '{print $2}'
eins
zwei
drei
vier
funf
sechs
sieben
acht
neun
zehn
$0
はすべての列 (正確には行全体)
$ cat num.txt | awk '{print $0}'
one eins yi 1 0
two zwei er 2 1
three drei san 3 1
four vier si 4 2
five funf wu 5 3
six sechs liu 6 5
seven sieben qi 7 8
eight acht ba 8 13
nine neun jiu 9 21
ten zehn shi 10 34
5列目の値が8
の行の1列目を表示
$ cat num.txt | awk '$5==8{print $1}'
seven
print
方法いろいろ
$ awk 'BEGIN{print "a" "b" 3}' # print 文に、"ab3" に連結された1つの文字列を渡す
ab3
$ awk 'BEGIN{print "a", "b", 3}' # print 文に3つの文字列を渡す (空白が入る)
a b 3
$ awk 'BEGIN{printf("%s|%s|%03d\n", "a", "b", 3)}' # printf 関数に4つの引数を与える
a|b|003
基本構造
awk '
条件式1{処理1}
条件式2{処理2}
'
処理の流れ:
- 入力行の1行目で条件1を評価し、真なら処理1を実行する。
- 入力行の1行目で条件2を評価し、真なら処理2を実行する。
- 入力行の2行目で条件1を評価し、真なら処理1を実行する。
- 入力行の2行目で条件2を評価し、真なら処理2を実行する。
なお、偽は ""
(空文字) と 0
で、真はそれ以外 (e.g. 1
"hoge"
) である。
変更した変数の値は後続の行でも引き継がれる (リセットされない)。
# 5列目の値が1の場合1列目を表示、3列目の値がsから始まる場合行全体を表示
$ cat num.txt | awk '$5==1{print $1} $3~/^s/{print $0}'
two
three
three drei san 3 1
four vier si 4 2
ten zehn shi 10 34
条件式を省略すると、暗黙的に真となる。
$ cat num.txt | awk '{print $1}'
one
two
three
four
five
six
seven
eight
nine
ten
$ cat num.txt | awk '{print $1}{print $4}'
one
1
two
2
three
3
four
4
five
5
six
6
seven
7
eight
8
nine
9
ten
10
処理を省略すると、暗黙的に {print $0}
となる。
$ cat num.txt | awk '$5==1'
two zwei er 2 1
three drei san 3 1
処理を省略して条件式だけを複数記述する場合、;
を使う。
$ cat num.txt | awk '$5==1; $1=="one"'
one eins yi 1 0
two zwei er 2 1
three drei san 3 1
特別な条件式 BEGIN
、END
がある。
$ cat num.txt | awk 'BEGIN{print "全ての処理の初めに1度だけ実行される"} $5==1; END{print "全ての処理の終わりに1度だけ実行される"}'
全ての処理の初めに1度だけ実行される
two zwei er 2 1
three drei san 3 1
全ての処理の終わりに1度だけ実行される
正規表現関連
正規表現マッチは ~
演算子で行う。
$ cat num.txt | awk '$0 ~ /r /' # 正規表現 "r " にマッチした行を検索
two zwei er 2 1
four vier si 4 2
$ cat num.txt | awk '/r /' # $0 ~ は省略可能
two zwei er 2 1
four vier si 4 2
sub("before", "after", 変数)
/ gsub
で 変数
を置換したものを 変数
に代入。
sub は最初のマッチのみ置換、gsub は全てのマッチを置換。
変数
を省略すると暗黙的に $0
となる。
関数の返り値は置換した個数。
$ echo hello world | awk '{print sub("l", "/", $0); print $0}'
1
he/lo world
$ echo hello world | awk '{print gsub("l", "/", $0); print $0}'
3
he//o wor/d
$ echo hello world | awk '{print sub("l", "/"); print $0}' # $0 は省略可
1
he/lo world
$ echo hello world | awk '{print gsub("l", "/"); print $0}' # $0 は省略可
3
he//o wor/d
if/else/else if
一般的なプログラミング言語と同じ。
""
(空文字) or 0
が偽、それ以外は真。
$ echo 2 | awk '{if($0==1){print "one"} else if($0==2){print "two"} else {print "three"}}'
two
for/while
$ awk 'BEGIN{for(i=0; i<3; i++){print i}}'
0
1
2
$ awk 'BEGIN{array[1]="one"; array[2]="two"; array[3]="three"; for(key in array){print key, array[key]}}'
1 one
2 two
3 three
$ awk 'BEGIN{a=3; while(a--){print a}}'
2
1
0
変数
変数の宣言は特に不要。
$ awk 'BEGIN{a=1; print a + 2}'
3
型は自動的に推論/変換される。
$ awk 'BEGIN{a="1"; print a + 2}'
3
文字列連結は、変数や値を空白で区切って並べるだけでいい。
(正確にはトークンが並んでいれば良いので、変数や値の切れ目が明確なら空白は必須ではない)
$ awk 'BEGIN{a="1"; print a "b"; c=2; d=c 3 4; print d}'
1b
234
未定義変数は、暗黙的に ""
(空文字) や 0
に評価される。
$ awk 'BEGIN{print a " <- a は空文字に評価"; print a + 0 " <- a は0に評価"}'
<- a は空文字に評価
0 <- a は0に評価
未定義変数に +=
や ++
等を使うことも可能。
$ awk 'BEGIN{a+=3; print a}'
3
コマンドライン引数から -v
で変数を渡すことが可能。
$ awk -v a=1 -v b=hoge 'BEGIN{print a, b}'
1 hoge
配列/辞書
配列 (array[key]
) は連想配列である。
key
は文字列でも数値でもいい。
$ awk 'BEGIN{a[1]="one"; a["two"]=2; print a[1], a["two"]}'
one 2
2次元配列は ,
で実現可能。
$ awk 'BEGIN{a["hoge",2]="fuga"; print a["hoge",2]}'
fuga
仕組み: 配列の添字に ,
が出現したとき、,
の左右が SUBSEP
組み込み変数 (0x1c
) で文字列連結される。
$ awk 'BEGIN{printf("%s", SUBSEP)}' | xxd -p # SUBSEP 組み込み変数のデフォルト値は 0x1c
1c
$ awk 'BEGIN{a["hoge",2]="fuga"; print a["hoge" SUBSEP 2]}' # 添字が "hoge" と 2 を SUBSEP で結合されたものとなっている
fuga
$ awk 'BEGIN{SUBSEP="/"; a["hoge",2]="fuga"; print a["hoge/2"]}' # SUBSEP を / に変更してみた
fuga
SUBSEP
は配列の添字以外では使われない。(と筆者は認識している)
print 1, 2
は print
文に 1
と 2
を渡しているのであり、SUBSEP
は働かない。
for(key in array)
で配列の全ての key を回す。
$ awk 'BEGIN{a["piyo"]=1; a[6]="hoge"; a[3]="fuga"; for(i in a){print i, a[i]}}'
6 hoge
piyo 1
3 fuga
組み込み変数
$0, $1, ...
: 列の値を格納。$(exp)
で exp に式を指定できる。代入も可能。
$ echo a b c | awk '{print $2}'
b
$ echo a b c | awk '{print $(1+1)}'
b
$ echo a b c | awk '{$2="bbbb"; print $0}'
a bbbb c
$ echo a b c | awk '$0=$2' # {print $2} と同じ
b
NR
: 行番号
$ yes | head -n4 | awk '{print NR, $0}'
1 y
2 y
3 y
4 y
NF: 現在行の列数
$ printf 'a b c\nd e\nf' | awk '{print NF, $0}'
3 a b c
2 d e
1 f
$ printf 'a b c\nd e\nf' | awk '{print NF, $NF}'
3 c
2 e
1 f
RS
: レコードの区切りを指定。デフォルトは改行。
$ echo $PATH | awk -v RS=: '{print $0}'
/usr/local/sbin
/usr/local/bin
/usr/sbin
/usr/bin
/sbin
/bin
ORS
: 出力レコードの区切りを指定。デフォルトは改行。
$ seq 10 | awk -v ORS=: '{print $0}'
1:2:3:4:5:6:7:8:9:10:
FS
: $1
とか $2
に分割する区切りを指定。デフォルトは空白やタブ。
正規表現が指定可能。
awk -F オプションでも指定可能。
$ head -n5 /etc/passwd | awk -v FS=: '{print $7, $1}'
/bin/bash root
/usr/sbin/nologin daemon
/usr/sbin/nologin bin
/usr/sbin/nologin sys
/bin/sync sync
$ head -n5 /etc/passwd | awk -F: '{print $7, $1}'
/bin/bash root
/usr/sbin/nologin daemon
/usr/sbin/nologin bin
/usr/sbin/nologin sys
/bin/sync sync
$ echo user@host:/path/to/dir/file | awk -v FS=[@:] '{print $1, $2, $3}' # 正規表現が扱える
user host /path/to/dir/file
OFS
: 出力の区切り文字を指定。
$ awk -v OFS=/ 'BEGIN{print 1, 2, 3}'
1/2/3
SUBSEP: 配列の添字の []
の中の ,
で文字列結合するときの区切り文字。デフォルトは 0x1c
。
上述の 配列辞書 を参照。
$ awk 'BEGIN{a["hoge",2]="fuga"; print a["hoge" SUBSEP 2]}'
fuga
ARGC: 引数のファイル名の個数+1。
ARGV: 引数のファイル名を格納した配列。
$ awk -v a=hoge 'BEGIN{print ARGC, ARGV[0], ARGV[1], ARGV[2]}' file1 file2
3 awk file1 file2
ENVIRON: 環境変数の配列。
$ awk 'BEGIN{print ENVIRON["USER"]}'
yabeenico
応用例
列を削除
列に代入すればいい
$ echo a b c d | awk '{$3=""; print $0}'
a b d
行を複製
$ seq 3 | awk '1;1'
1
1
2
2
3
3
代入演算子 =
を活用
=
演算子は +
-
*
/
と同様に二項演算子である。
(2+2)
の評価結果が4
であるのと同様に、(a=4)
の評価結果も4
である。
$ awk 'BEGIN{print 1 + (a=3)}'
4
$0 への代入を条件式にすると print
と同じことができる。
ただし、その結果 $0
が偽になったら print
されないので注意。
$ echo a b c | awk '$0=$2' # {print $2} と同じ
b
cat と同じことをする
$ awk 1 num.txt num.txt
one eins yi 1 0
two zwei er 2 1
three drei san 3 1
four vier si 4 2
five funf wu 5 3
six sechs liu 6 5
seven sieben qi 7 8
eight acht ba 8 13
nine neun jiu 9 21
ten zehn shi 10 34
one eins yi 1 0
two zwei er 2 1
three drei san 3 1
four vier si 4 2
five funf wu 5 3
six sechs liu 6 5
seven sieben qi 7 8
eight acht ba 8 13
nine neun jiu 9 21
ten zehn shi 10 34
重複削除 (順番保存)
$ printf "a\nb\na\nc" | awk '!a[$0]++' # 3行目の a が消える
a
b
c
解説
未定義変数は0に評価されるため、a が未定義のとき a++
-> a=a+1
-> a=1
となる。
また、a++
自体は加算前の値に評価され、++a
は加算後の値に評価される。
(ので本当は ++a
だけが a=a+1
といえる)
$ awk 'BEGIN{print a++; print a}'
0
1
$ awk 'BEGIN{print ++a; print a}'
1
1
未定義キーを指定された配列も同様の挙動となる。(array[key]++
)
$ awk 'BEGIN{print a["hoge"]++; print a["hoge"]; print ++a["fuga"]; print a["fuga"]}'
0
1
1
1
つまり、a[$0]++
で行が出現した回数をカウントできることが分かる。
$ printf "a\nb\na\nc" | awk '{print a[$0]++, $0}'
0 a
0 b
1 a
0 c
否定することで初出時に1となる。
$ printf "a\nb\na\nc" | awk '{print !a[$0]++, $0}'
1 a
1 b
0 a
1 c
あとはそれを条件式に利用すればいい。({print $0}
は省略可能)
$ printf "a\nb\na\nc" | awk '!a[$0]++'
a
b
c
PATH
の重複削除への応用例。
PATH
は順番の保存が必要なので sort | uniq
は使えない。
paste -sd:
で、行を :
で結合
$ export PATH=$(printf "$PATH"|awk -vRS=: '!a[$0]++'|paste -sd:)
$ seq 10 | paste -sd:
1:2:3:4:5:6:7:8:9:10
ログ整形
/var/log/auth.log
の Accepted
な鍵の fingerprint を表示する。
$ head /var/log/auth.log
Nov 13 01:17:01 myhost CRON[2443333]: pam_unix(cron:session): session closed for user root
Nov 13 01:22:25 myhost sshd[2443853]: Accepted publickey for user1 from 192.168.3.2 port 50632 ssh2: RSA SHA256:ID3jU1U/A11prLccA76POqNAq4sW/wflswuZkRbllu1
Nov 13 01:22:25 myhost sshd[2443852]: Accepted publickey for user1 from 192.168.3.2 port 50630 ssh2: RSA SHA256:ID3jU1U/A11prLccA76POqNAq4sW/wflswuZkRbllu1
Nov 13 01:22:25 myhost sshd[2443853]: pam_unix(sshd:session): session opened for user user1 by (uid=0)
Nov 13 01:22:25 myhost sshd[2443852]: pam_unix(sshd:session): session opened for user user1 by (uid=0)
Nov 13 01:22:25 myhost systemd-logind[711]: New session 7150 of user user1.
Nov 13 01:22:25 myhost systemd-logind[711]: New session 7151 of user user1.
Nov 13 01:22:25 myhost systemd: pam_unix(systemd-user:session): session opened for user user1 by (uid=0)
Nov 13 01:22:26 myhost sshd[2444092]: Received disconnect from 192.168.3.2 port 50632:11: disconnected by user
Nov 13 01:22:26 myhost sshd[2444092]: Disconnected from user user1 192.168.3.2 port 50632
$ head /var/log/auth.log | awk '/Accepted/&&$0=$NF'
SHA256:ID3jU1U/A11prLccA76POqNAq4sW/wflswuZkRbllu1
SHA256:ID3jU1U/A11prLccA76POqNAq4sW/wflswuZkRbllu1