LoginSignup
0
2

More than 1 year has passed since last update.

awk 基礎

Last updated at Posted at 2022-12-02

hello

$ echo hello | awk '{print $0}'
hello

サンプルデータ (num.txt) の準備

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 で入力終了)
num.txt の内容を確認 (cat)
$ 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 方法いろいろ

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. 入力行の1行目で条件2を評価し、真なら処理2を実行する。
  3. 入力行の2行目で条件1を評価し、真なら処理1を実行する。
  4. 入力行の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

特別な条件式 BEGINEND がある。

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 が偽、それ以外は真。

if/else/else if
$ echo 2 | awk '{if($0==1){print "one"} else if($0==2){print "two"} else {print "three"}}'
two

for/while

for
$ awk 'BEGIN{for(i=0; i<3; i++){print i}}'
0
1
2
for(key in array)
$ 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
while
$ 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 に評価される。

未定義変数は "" or 0
$ awk 'BEGIN{print a " <- a は空文字に評価"; print a + 0 " <- a は0に評価"}'
 <- a は空文字に評価
0 <- a は0に評価

未定義変数に +=++ 等を使うことも可能。

未定義変数へ加算代入
$ awk 'BEGIN{a+=3; print a}'
3

コマンドライン引数から -v で変数を渡すことが可能。

コマンドライン引数で変数を定義 (-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) で文字列連結される。

SUBSEP
$ 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, 2print 文に 12 を渡しているのであり、SUBSEP は働かない。

for(key in array) で配列の全ての key を回す。

for(key in array)
$ 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: 行番号

NR
$ yes | head -n4 | awk '{print NR, $0}'
1 y
2 y
3 y
4 y

NF: 現在行の列数

NF
$ printf 'a b c\nd e\nf' | awk '{print NF, $0}'
3 a b c
2 d e
1 f
$NF: 最後の列を表示
$ printf 'a b c\nd e\nf' | awk '{print NF, $NF}'
3 c
2 e
1 f

RS: レコードの区切りを指定。デフォルトは改行。

RS
$ echo $PATH | awk -v RS=: '{print $0}'
/usr/local/sbin
/usr/local/bin
/usr/sbin
/usr/bin
/sbin
/bin

ORS: 出力レコードの区切りを指定。デフォルトは改行。

ORS
$ seq 10 | awk -v ORS=: '{print $0}'
1:2:3:4:5:6:7:8:9:10:

FS: $1 とか $2 に分割する区切りを指定。デフォルトは空白やタブ。
正規表現が指定可能。
awk -F オプションでも指定可能。

FS
$ 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: 出力の区切り文字を指定。

OFS
$ awk -v OFS=/ 'BEGIN{print 1, 2, 3}'
1/2/3

SUBSEP: 配列の添字の [] の中の , で文字列結合するときの区切り文字。デフォルトは 0x1c
上述の 配列辞書 を参照。

SUBSEP
$ awk 'BEGIN{a["hoge",2]="fuga"; print a["hoge" SUBSEP 2]}'
fuga

ARGC: 引数のファイル名の個数+1。
ARGV: 引数のファイル名を格納した配列。

ARGC, ARGV
$ awk -v a=hoge 'BEGIN{print ARGC, ARGV[0], ARGV[1], ARGV[2]}' file1 file2
3 awk file1 file2

ENVIRON: 環境変数の配列。

ENVIRON
$ awk 'BEGIN{print ENVIRON["USER"]}'
yabeenico

応用例

列を削除

列に代入すればいい

3列目を削除
$ 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 されないので注意。

$0 に代入すると print と同じことができる
$ echo a b c | awk '$0=$2' # {print $2} と同じ
b

cat と同じことをする

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 といえる)

a++, ++a
$ awk 'BEGIN{print a++; print a}'
0
1

$ awk 'BEGIN{print ++a; print a}'
1
1

未定義キーを指定された配列も同様の挙動となる。(array[key]++)

array[key]++, ++array[key]
$ awk 'BEGIN{print a["hoge"]++; print a["hoge"]; print ++a["fuga"]; print a["fuga"]}'
0
1
1
1

つまり、a[$0]++ で行が出現した回数をカウントできることが分かる。

a[$0]++
$ printf "a\nb\na\nc" | awk '{print a[$0]++, $0}'
0 a
0 b
1 a
0 c

否定することで初出時に1となる。

!a[$0]++
$ 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: で、行を : で結合

PATH の重複削除
$ 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.logAccepted な鍵の fingerprint を表示する。

/var/log/auth.log
$ 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
/var/log/auth.log の Accepted な fingerprint を表示
$ head /var/log/auth.log | awk '/Accepted/&&$0=$NF'
SHA256:ID3jU1U/A11prLccA76POqNAq4sW/wflswuZkRbllu1
SHA256:ID3jU1U/A11prLccA76POqNAq4sW/wflswuZkRbllu1
0
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
2