sed 、awk 和 grep 都是 Linux 系统下的非交互式的文本处理工具。

sed 的基本处理单位为记录 (record),简单理解的话就是文件的行;而 awk 的基本处理单位为域 (field),即文件的逻辑列。

以下内容大部分整理自 小明 from Github.com,有改动。

sed 用法

语法 1 简单打印

格式: sed [options] {sed-commands} {input-file}

系统里 /etc/passwd 文件的内容:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
root:x:0:0:root:/root:/bin/zsh
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
sys:x:3:3:sys:/dev:/usr/sbin/nologin
sync:x:4:65534:sync:/bin:/bin/sync
games:x:5:60:games:/usr/games:/usr/sbin/nologin
man:x:6:12:man:/var/cache/man:/usr/sbin/nologin
lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin
mail:x:8:8:mail:/var/mail:/usr/sbin/nologin
news:x:9:9:news:/var/spool/news:/usr/sbin/nologin
uucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologin
proxy:x:13:13:proxy:/bin:/usr/sbin/nologin
www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin
backup:x:34:34:backup:/var/backups:/usr/sbin/nologin
list:x:38:38:Mailing List Manager:/var/list:/usr/sbin/nologin
irc:x:39:39:ircd:/var/run/ircd:/usr/sbin/nologin
gnats:x:41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/usr/sbin/nologin
nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin
systemd-timesync:x:100:102:systemd Time Synchronization,,,:/run/systemd:/bin/false
systemd-network:x:101:103:systemd Network Management,,,:/run/systemd/netif:/bin/false
systemd-resolve:x:102:104:systemd Resolver,,,:/run/systemd/resolve:/bin/false
systemd-bus-proxy:x:103:105:systemd Bus Proxy,,,:/run/systemd:/bin/false
_apt:x:104:65534::/nonexistent:/bin/false
rtkit:x:105:109:RealtimeKit,,,:/proc:/bin/false
dnsmasq:x:106:65534:dnsmasq,,,:/var/lib/misc:/bin/false
avahi-autoipd:x:107:110:Avahi autoip daemon,,,:/var/lib/avahi-autoipd:/bin/false
messagebus:x:108:111::/var/run/dbus:/bin/false
usbmux:x:109:46:usbmux daemon,,,:/var/lib/usbmux:/bin/false
lightdm:x:111:115:Light Display Manager:/var/lib/lightdm:/bin/false
......(省略)......

例子:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# -n 表示取消默认输出 (默认输出将会打印出整个文件), p 表示打印行
➜ sed -n 'p' /etc/passwd
root:x:0:0:root:/root:/bin/zsh
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
sys:x:3:3:sys:/dev:/usr/sbin/nologin
sync:x:4:65534:sync:/bin:/bin/sync
games:x:5:60:games:/usr/games:/usr/sbin/nologin
man:x:6:12:man:/var/cache/man:/usr/sbin/nologin
lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin
mail:x:8:8:mail:/var/mail:/usr/sbin/nologin
news:x:9:9:news:/var/spool/news:/usr/sbin/nologin
......(省略)......

# 只打印第三行
➜ sed -n '3p' /etc/passwd
bin:x:2:2:bin:/bin:/usr/sbin/nologin

# 打印第 1 到 3 行
➜ sed -n '1,3p' /etc/passwd
root:x:0:0:root:/root:/bin/zsh
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin

打印第 1 行 和 第 3 行语法就不一样了,后面再看。

语法 2 执行 sed 脚本

格式:sed [options] -f {sed-commands-in-a-file} {input-file}

例子:

1
2
3
4
5
6
7
8
# 打印以 root 开头或者 nobody 开头的行
➜ cat sed_example_1.sed
/^root/ p
/^nobody/ p

➜ sed -n -f sed_example_1.sed /etc/passwd
root:x:0:0:root:/root:/bin/zsh
nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin

语法 3 匹配内容打印

格式: sed [options] -e {sed-command-1} -e {sed-command-2} {input-file}

例子:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
# 打印以 root 开头或者 nobody 开头的行
➜ sed -n -e '/^root/ p' -e '/^nobody/ p' /etc/passwd
root:x:0:0:root:/root:/bin/zsh
nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin

#或者
➜ sed -n \
-e '/^root/ p' \
-e '/^nobody/ p' \
/etc/passwd

如果要打印除了匹配之外的所有行,只需要在 p 前面加上 !。我们举个例子,由于 /etc/passwd 每一行都会有 uid 等等这些数字内容,所以我们现在匹配打印没有数字的行,理论上应该输出为空:

1
2
3
4
5
6
➜ sed -n '/[0-9]/ !p' /etc/passwd 
(无输出)

# 确实没有输出,为了确保命令没有出错我们还可以检查一次命令的结果码:echo $?
0

语法 4 一次执行多个命令

格式:

1
2
3
4
sed [options] '{
sed-command-1
sed-command-2
}' input-file

注意这里要严格按照这个空行规则来,一行一个命令的写,不然就会报错。例子:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
# 打印以 root 开头或者 sync 结尾的行
sed -n '{
/^root/ p
/sync$/ p
}' /etc/passwd
root:x:0:0:root:/root:/bin/zsh
sync:x:4:65534:sync:/bin:/bin/sync

# 前面提到的打印第 1 行 和 第 3 行也可以实现了
# 首先看看 1~3 行的内容
➜ sed '1,3 p' /etc/passwd -n
root:x:0:0:root:/root:/bin/zsh
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
# 然后只看第 1 和第 3 行
➜ sed '{                    
1p 
3p
}' /etc/passwd -n
root:x:0:0:root:/root:/bin/zsh
bin:x:2:2:bin:/bin:/usr/sbin/nologin

sed 流

利用 sed 流可以实现文件操作:

  1. 执行
  2. 打印
  3. 重复

源文件 source.txt 内容如下:

101,Ian Bicking,Mozilla
102,Hakim El Hattab,Whim
103,Paul Irish,Google
104,Addy Osmani,Google
105,Chris Wanstrath,Github
106,Mattt Thompson,Heroku
107,Ask Solem Hoel,VMware

范围

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# 从第 1 行开始,步长为 2, 即所有的奇数行
➜ sed -n '1~2 p' source.txt
101,Ian Bicking,Mozilla
103,Paul Irish,Google
105,Chris Wanstrath,Github
107,Ask Solem Hoel,VMware                                                                                                       

# 从第 2 行开始,步长为 3
➜ sed -n '2~3 p' source.txt
102,Hakim El Hattab,Whim
105,Chris Wanstrath,Github

模式匹配

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
# 寻找包含 Paul 的行
➜ sed -n '/Paul/ p' source.txt
103,Paul Irish,Google

# 从找到 Paul 开始打印到第 5 行(潜在意思是只在第 1 行开始到第 5 行中找)
➜ sed -n '/Paul/,5 p' source.txt
103,Paul Irish,Google
104,Addy Osmani,Google
105,Chris Wanstrath,Github

# 从匹配 Paul 行打印达匹配 Addy 的行
➜ sed -n '/Paul/,/Addy/ p' source.txt
103,Paul Irish,Google
104,Addy Osmani,Google

# 匹配 Paul 行再多输出 2 行
➜ sed -n '/Paul/,+2 p' source.txt
103,Paul Irish,Google
104,Addy Osmani,Google
105,Chris Wanstrath,Github

这里我就想,如果我想匹配到 Paul 然后往前反着打印上面 2 行呢?我去 Google 了一下,似乎这个问题就没有那么简单了,找到一个最相近的:SED: addressing two lines before match ,但这个是打印匹配往回数的第 2 行,不是我说的匹配再往前打印两行:

1
2
3
# 匹配 Paul 往前数的第 2 行
➜ sed -n "1N;2N;/Paul[^\n]*$/P;N;D" source.txt
101,Ian Bicking,Mozilla

删除行

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
# 删除所有行
➜ sed 'd' source.txt
(无输出)

# 只删除第二行
➜ sed '2 d' source.txt 
101,Ian Bicking,Mozilla
103,Paul Irish,Google
104,Addy Osmani,Google
105,Chris Wanstrath,Github
106,Mattt Thompson,Heroku
107,Ask Solem Hoel,VMware

# 删除第一到第四行
➜ sed '1,4 d' source.txt
105,Chris Wanstrath,Github
106,Mattt Thompson,Heroku
107,Ask Solem Hoel,VMware

# 删除奇数行
➜ sed '1~2 d' source.txt
102,Hakim El Hattab,Whim
104,Addy Osmani,Google
106,Mattt Thompson,Heroku

# 删除符合 Paul 到 Addy 的行
➜ sed '/Paul/,/Addy/d' source.txt
101,Ian Bicking,Mozilla
102,Hakim El Hattab,Whim
105,Chris Wanstrath,Github
106,Mattt Thompson,Heroku
107,Ask Solem Hoel,VMware

# 删除空行(就是内容为空的行,即开头 `^` 和 结尾 `$` 之间无内容)
➜ sed '/^$/ d' source.txt

# 删除用 `#` 注释的行(即 `#` 开头的行)
➜ sed '/^#/ d' source.txt

重定向

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# 将 source.txt 内容重定向写到 output.txt
➜ sed 'w output.txt' source.txt
# 重定向写到 output.txt, 并且不再终端打印
➜ sed -n 'w output.txt' source.txt

# 只写第二行
➜ sed -n '2 w output.txt' source.txt
# 写一到四行到 output.txt
➜ sed -n '1,4 w output.txt'
# 写匹配到 Ask 的行到结尾行到 output.txt
➜ sed -n '/Ask/,$ w output.txt'

替换

格式为:

1
sed '[address-range|pattern-range] s/original-string/replacement-string/[substitute-flags]' inputfile

例子:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
# 替换 Google 为 Github
➜ sed 's/Google/Github/' source.txt
101,Ian Bicking,Mozilla
102,Hakim El Hattab,Whim
103,Paul Irish,Github
104,Addy Osmani,Github
105,Chris Wanstrath,Github
106,Mattt Thompson,Heroku
107,Ask Solem Hoel,VMware

# 替换匹配 Addy 的行里面的 Google 为 Github
➜ sed '/Addy/s/Google/Github/' source.txt
101,Ian Bicking,Mozilla
102,Hakim El Hattab,Whim
103,Paul Irish,Google
104,Addy Osmani,Github
105,Chris Wanstrath,Github
106,Mattt Thompson,Heroku
107,Ask Solem Hoel,VMware

# 默认 s 只会替换一行中的第一个匹配项
➜ sed '1 s/a/A/'  source.txt|head -1
101,IAn Bicking,Mozilla
# g 可以替换每行的全部符合
➜ sed '1 s/a/A/g'  source.txt|head -1
101,IAn Bicking,MozillA

# 我们也可以直接指定想要替换的第 N 个匹配项 , 这里是第二个
➜ sed '1 s/a/A/2'  source.txt|head -1
101,Ian Bicking,MozillA

# 使用 w 将能够替换的行重定向写到 output.txt
➜ sed -n 's/Mozilla/Github/w output.txt' source.txt
➜ cat output.txt 
101,Ian Bicking,Github

# 还可以使用 i 忽略匹配的大小写
➜ sed '1 s/iaN/IAN/i'  source.txt|head -1
101,IAN Bicking,Mozilla

利用替换功能我们还能做很多操作:

1
2
3
4
5
6
7
8
➜ cat files.txt 
/etc/passwd
/etc/group

# 给每行前添加一个 `ls -l `,行后添加 ` | head -l`
➜ sed 's/\(.*\)/ ls -l \1 | head -l/' files.txt 
ls -l /etc/passwd | head -l
ls -l /etc/group | head -l

这个命令首先是利用 .* 匹配一个非空行,写成 \(.*\) 是因为括号要转义。匹配每一行后把每一行内容都替换为 ls -l (注意 l 后面有个空格) + 原内容 (\1 指代那一行行原来的内容) + | head -l (同样注意 | 前有空格),所以整体意思就是在每一个非空行前后各加了内容。

sed 还可以用来直接执行文件内容里的命令,比如现在 files.txt 添加一行内容 echo "Hello!",然后我们查找 files.txt 文件里有 echo 命令的行并直接执行这一行命令:

1
2
3
4
5
6
7
8
9
➜ cat files.txt 
/etc/passwd
/etc/group
echo "Hello!"

➜ sed '/echo/ e' files.txt   
/etc/passwd
/etc/group
Hello!

可以看到前两行由于不符合匹配直接打印原内容,而第 3 行匹配到 echo 之后直接执行了这一行命令并打印输出结果。 结合匹配替换和执行,更高级的功能也能实现:

1
2
3
4
5
6
7
➜ cat files.txt 
/etc/passwd
/etc/group

➜ sed 's/^/ls -l /e' files.txt
-rw-r--r-- 1 root root 1627 Oct 14 14:30 /etc/passwd
-rw-r--r-- 1 root root 807 Oct 14 14:30 /etc/group

整个逻辑是首先匹配替换行首为 ls -l,即相当于在每一行行首添加 ls -l,然后把每一行当作命令直接执行。

另外,sed 的分隔符不一定要是 /,比如我们换成 | @ ! 等等都是可以的:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
➜ sed 's|^|ls -l |e' files.txt
-rw-r--r-- 1 root root 2250 Aug 16 13:32 /etc/passwd
-rw-r--r-- 1 root root 1040 Nov 18 19:03 /etc/group

➜ sed 's@^@ls -l @e' files.txt
-rw-r--r-- 1 root root 2250 Aug 16 13:32 /etc/passwd
-rw-r--r-- 1 root root 1040 Nov 18 19:03 /etc/group

➜ sed 's!^!ls -l !e' files.txt
-rw-r--r-- 1 root root 2250 Aug 16 13:32 /etc/passwd
-rw-r--r-- 1 root root 1040 Nov 18 19:03 /etc/group

但是显然这样有时候会降低代码可读性和增加误解。

替换覆盖

我们可以在替换完成的内容上再次替换,而且一个命令就行,不需要做两次。 比如我们先 Google 替换为 Github,然后又把 Git 替换为 git:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
➜ sed '{
s/Google/Github/
s/Git/git/ 
}' source.txt
101,Ian Bicking,Mozilla
102,Hakim El Hattab,Whim
103,Paul Irish,github
104,Addy Osmani,github
105,Chris Wanstrath,github
106,Mattt Thompson,Heroku
107,Ask Solem Hoel,VMware

& 代表匹配到的内容

& 代表匹配到的内容(注意不是匹配到的行的内容)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
➜ sed 's/^[0-9][0-9][0-9]/[&]/g' source.txt
[101],Ian Bicking,Mozilla
[102],Hakim El Hattab,Whim
[103],Paul Irish,Google
[104],Addy Osmani,Google
[105],Chris Wanstrath,Github
[106],Mattt Thompson,Heroku
[107],Ask Solem Hoel,VMware

➜ sed 's/^.*/[&]/' source.txt
[101,Ian Bicking,Mozilla]
[102,Hakim El Hattab,Whim]
[103,Paul Irish,Google]
[104,Addy Osmani,Google]
[105,Chris Wanstrath,Github]
[106,Mattt Thompson,Heroku]
[107,Ask Solem Hoel,VMware]

➜ sed 's/^.*/<<&>>/g' source.txt
<<101,Ian Bicking,Mozilla>>
<<102,Hakim El Hattab,Whim>>
<<103,Paul Irish,Google>>
<<104,Addy Osmani,Google>>
<<105,Chris Wanstrath,Github>>
<<106,Mattt Thompson,Heroku>>
<<107,Ask Solem Hoel,VMware>>

利用这个我们可以重写上面那个行前行后添加内容的例子,或者反过来用前面的例子写上面的:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
➜ sed 's/\(.*\)/ls -l & | head -l/' files.txt
ls -l /etc/passwd | head -l
ls -l /etc/group | head -l

➜ sed -e "s/^\(.*\)/[\1]/" source.txt 
[101,Ian Bicking,Mozilla]
[102,Hakim El Hattab,Whim]
[103,Paul Irish,Google]
[104,Addy Osmani,Google]
[105,Chris Wanstrath,Github]
[106,Mattt Thompson,Heroku]
[107,Ask Solem Hoel,VMware]

正则

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
# ^ 表示匹配行首
➜ sed -n '/^101/ p' source.txt      
101,Ian Bicking,Mozilla
# $ 表示匹配行末
➜ sed -n '/Github$/ p' source.txt 
105,Chris Wanstrath,Github

# . 表示单个字符 , 下面的例子匹配一个逗号然后 I 然后 2 个单字符
➜ sed -n '/,I../ p' source.txt
101,Ian Bicking,Mozilla
# *表示匹配 0 个或者多个, \+ 表示匹配一个或者多个, \? 表示匹配 0 个或者 1 个

# [0-9] 表示匹配数字, 下面匹配包含 3 或者 4 的行
➜ sed -n '/[34]/ p ' source.txt      
103,Paul Irish,Google
104,Addy Osmani,Google

# - 表示范围 , 这里匹配 3,4,5
➜ sed -n '/[3-5]/ p ' source.txt
103,Paul Irish,Google
104,Addy Osmani,Google
105,Chris Wanstrath,Github

# | 表示或者的关系
➜ sed -n '/102\|103/ p ' source.txt
102,Hakim El Hattab,Whim
103,Paul Irish,Google

➜ cat numbers.txt 
1
12
123
1234
12345
123456
# {m} 表示前面的匹配的重复次数
➜ sed -n '/^[0-9]\{5\}$/ p' numbers.txt
12345
# {m,n} 表示匹配 m-n 的次数都算
sed -n '/^[0-9]\{3,5\}$/ p' numbers.txt
123
1234
12345

# 删除所有注释行和空行
➜ sed -e 's/#.*//' -e '/^$/ d' /etc/profile
							
# \1 表示第一个正则匹配到的数据
➜ sed 's/\([^,]*\).*/\1/g' source.txt |head -1
101
# 给每个单词第一个字母加括号echo "Hello World" | sed 's/\([A-Z]\)/\(\1\)/g'
(H)ello (W)orld
# 加方括号echo "Hello World" | sed 's/\([A-Z]\)/\[\1\]/g'
[H]ello [W]orld

其他

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
# \l 能将后面的一个字符变成小写
➜ sed 's/Ian/IAN/' source.txt|head -1               
101,IAN Bicking,Mozilla
➜ sed 's/Ian/IA\lN/' source.txt|head -1 
101,IAn Bicking,Mozilla
# \L 能将后面的字符都变成小写
➜ sed 's/Ian/I\LAN/' source.txt|head -1
101,Ian Bicking,Mozilla
# \u 能将后面的一个字符变成大写
➜ sed 's/Ian/IA\un/' source.txt|head -1
101,IAN Bicking,Mozilla
# \U 能将后面的字都变成大写
➜ sed 's/Ian/\Uian/' source.txt|head -1 
101,IAN Bicking,Mozilla

# \E 能打断 \L 或者 \U 改变大小写
➜ sed 's/Ian/\Uia\En/' source.txt|head -1
101,IAn Bicking,Mozilla

sed 可执行脚本

1
2
3
4
5
6
7
8
➜ cat testscript.sed
#!/bin/sed -nf
/root/ p
/nobody/ p
➜ chmod u+x testscript.sed
➜ ./testscript.sed /etc/passwd 
root:x:0:0:root:/root:/bin/zsh
nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin

sed 修改源文件和备份

1
2
3
4
5
#-i 会修改源文件 , 但是可以同时使用 bak 备份
➜ sed -i.bak 's/Ian/IAN/' source.txt 
# or
➜ sed --in-place=.bak 's/Ian/IAN/' source.txt 
# 这样备份一个修改前的文件为 source.txt.bak

行后增加和行前插入

语法格式:

  • 行后增加: sed '[address] a the-line-to-append' input-file
  • 行前插入: sed '[address] i the-line-to-insert' input-file

例子:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
➜ sed '2 a 108,Donald Stufft, Nebula' source.txt
101,IAN Bicking,Mozilla
102,Hakim El Hattab,Whim
108,Donald Stufft, Nebula
103,Paul Irish,Google
104,Addy Osmani,Google
105,Chris Wanstrath,Github
106,Mattt Thompson,Heroku
107,Ask Solem Hoel,VMware

➜ sed '2 i 108,Donald Stufft, Nebula' source.txt
101,IAN Bicking,Mozilla
108,Donald Stufft, Nebula
102,Hakim El Hattab,Whim
103,Paul Irish,Google
104,Addy Osmani,Google
105,Chris Wanstrath,Github
106,Mattt Thompson,Heroku
107,Ask Solem Hoel,VMware

修改行

格式:sed '[address] c the-line-to-insert' input-file

例子:

1
2
3
4
5
6
7
8
9
# 修改含有 Paul 的行
➜ sed '/Paul/ c 108,Donald Stufft, Nebula' source.txt
101,IAN Bicking,Mozilla
102,Hakim El Hattab,Whim
108,Donald Stufft, Nebula
104,Addy Osmani,Google
105,Chris Wanstrath,Github
106,Mattt Thompson,Heroku
107,Ask Solem Hoel,VMware

其他用法:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
# = 显示行号sed = source.txt
1
101,Ian Bicking,Mozilla
2
102,Hakim El Hattab,Whim
3
103,Paul Irish,Google
4
104,Addy Osmani,Google
5
105,Chris Wanstrath,Github
6
106,Mattt Thompson,Heroku
7
107,Ask Solem Hoel,VMware

# y 转换字符 , 这里 I 会转化成 i,B 转换成 b
➜ sed 'y/IB/ib/' source.txt |head -1
101,iAN bicking,Mozilla

awd

示例文件 items.txt,列分别是 id, 描述 , 价钱和库存:

101,HD Camcorder,Video,210,10
102,Refrigerator,Appliance,850,2
103,MP3 Player,Audio,270,15
104,Tennis Racket,Sports,190,20
105,Laser Printer,Office,475,5

示例文件 items-sold.txt, 列分别是 id 和 1-6 月的销售情况

101 2 10 5 8 10 12
102 0 1 4 3 0 2
103 10 6 11 20 5 13
104 2 3 4 0 6 5
105 10 2 5 7 12 6

语法 1

-F 表示设置分隔符 , 不指定就是默认为空字符 , Fs 即 field seperator

1
2
3
awk -Fs '/pattern/ {action}' input-file
# or
awk -Fs '{action}' intput-file

用 : 分割 /etc/passwd 文件内容,查找匹配 systemd 的行并且打印以冒号作为分割后的第一列

1
2
3
4
5
➜ awk -F: '/systemd/ {print $1}' /etc/passwd
systemd-timesync
systemd-network
systemd-resolve
systemd-coredump

awk 数据结构

  1. BEGIN {awk-commands} 会在执行 awk body 之前执行, 而且只会在最开始执行一次
  2. /pattern/ {action} 是 body 部分,也就是 awk 要执行的主体 , 比如文件内容有 10 行 , 那么这个主体就调用 10 次
  3. END {awk-commands} 会在执行完 body 之后执行 , 也只会在最末尾执行一次

下面的例子,我们在 /etc/passwd 里查找 systemd 并且打印第 1 列,但最开始和最末尾我们分别打印一次 “—-header—-” 和 “—-footer—-”

1
2
3
4
5
6
7
8
➜ awk -F: 'BEGIN {print "----header----"} /systemd/ {print $1} \
END {print "----footer----"}' /etc/passwd
----header----
systemd-timesync
systemd-network
systemd-resolve
systemd-bus-proxy
----footer----

当然也可以选择只使用其中一种或者几种

1
2
3
4
5
6
➜ awk -F: 'BEGIN {print "UID"} {print $3}' /etc/passwd | head -3
UID
0
1
➜ awk 'BEGIN {print "Hello World!"}'
Hello World!

print

默认 print 就是打印文件全部内容,$n 表示打印第 n 列,n 为 0 时表示全部列

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
➜ awk '{print}' source.txt 
101,Ian Bicking,Mozilla
102,Hakim El Hattab,Whim
103,Paul Irish,Google
104,Addy Osmani,Google
105,Chris Wanstrath,Github
106,Mattt Thompson,Heroku
107,Ask Solem Hoel,VMware

➜ awk -F ',' '{print $2}' source.txt
Ian Bicking
Hakim El Hattab
Paul Irish
Addy Osmani
Chris Wanstrath
Mattt Thompson
Ask Solem Hoel
# or
➜ awk -F "," '{print $2}' source.txt
➜ awk -F, '{print $2}' source.txt

可以用 BEGIN 和 END 来格式化输出:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
➜ awk -F "," 'BEGIN {print "--------------\nName\tComp\n--------------"} \
{print $2,"\t",$3}\
END {print "--------------"}' source.txt

--------------
Name	Comp
--------------
Ian Bicking 	 Mozilla
Hakim El Hattab 	 Whim
Paul Irish 	 Google
Addy Osmani 	 Google
Chris Wanstrath 	 Github
Mattt Thompson 	 Heroku
Ask Solem Hoel 	 VMware
--------------

➜ awk -F, 'BEGIN {print "--------------\nName\tCompany\n--------------"} \
{print $2, "\t", $3} \ 
{print "---------------"}' source.txt 

--------------
Name	Company
--------------
Ian Bicking 	 Mozilla
---------------
Hakim El Hattab 	 Whim
---------------
Paul Irish 	 Google
---------------
Addy Osmani 	 Google
---------------
Chris Wanstrath 	 Github
---------------
Mattt Thompson 	 Heroku
---------------
Ask Solem Hoel 	 VMware
---------------

模式匹配

1
2
3
4
5
6
# 用逗号做分隔符 , 打印第二和第三列
➜ awk -F ',' '/Whim/ {print $2, $3}' source.txt
Hakim El Hattab Whim
# 可以加点格式化语句
➜ awk -F, '/Whim/ {print "Whim\47s full name:",$2}' source.txt
Whim's full name: Hakim El Hattab

打印单引号 ' 非常 tricky,这个算是最简单的办法吧。

awk 内置变量

这里提到的字段、记录其实就是最开始提到的 field、record,通俗理解就是列、行。

1. FS:输入字段分隔符

设置分隔符:输入字段分隔符 FS

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
➜ awk -F, '{print $2,$3}' source.txt 
Ian Bicking Mozilla
Hakim El Hattab Whim
Paul Irish Google
Addy Osmani Google
Chris Wanstrath Github
Mattt Thompson Heroku
Ask Solem Hoel VMware
# 可以使用内置的 FS - 输入字段分隔符 实现相同的功能
➜awk 'BEGIN {FS=","} {print $2,$3}' source.txt            
Ian Bicking Mozilla
Hakim El Hattab Whim
Paul Irish Google
Addy Osmani Google
Chris Wanstrath Github
Mattt Thompson Heroku
Ask Solem Hoel VMware
➜ cat source-multiple-fs.txt
101,Ian Bicking:Mozilla%
102,Hakim El Hattab:Whim%
103,Paul Irish:Google%
104,Addy Osmani:Google%
105,Chris Wanstrath:Github%
106,Mattt Thompson:Heroku%
107,Ask Solem Hoel:VMware%
# 发现上面的分隔符有三种 : 逗号分号和百分号 , 这样就可以这样使用 :
➜ awk 'BEGIN {FS="[,:%]"} {print $2,$3}' source-multiple-fs.txt
Ian Bicking Mozilla
Hakim El Hattab Whim
Paul Irish Google
Addy Osmani Google
Chris Wanstrath Github
Mattt Thompson Heroku
Ask Solem Hoel VMware

2. OFS:输出字段分隔符

设置输出时的分隔符:输出字段分隔符 OFS

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
➜ awk -F, '{print $2":"$3}' source.txt
Ian Bicking:Mozilla
Hakim El Hattab:Whim
Paul Irish:Google
Addy Osmani:Google
Chris Wanstrath:Github
Mattt Thompson:Heroku
Ask Solem Hoel:VMware
➜ awk -F, 'BEGIN {OFS=":"} {print $2":"$3}' source.txt
Ian Bicking:Mozilla
Hakim El Hattab:Whim
Paul Irish:Google
Addy Osmani:Google
Chris Wanstrath:Github
Mattt Thompson:Heroku
Ask Solem Hoel:VMware

3. RS:输入记录分隔符

注意记录指行,前面就说过。 现在有一个文件 source-one-line.txt 内容为:

1,one:2,two:3,three:4,four

想输出

one two

这样的效果。

借用记录分隔符 RS 把单行内容分割为多行,然后再按 -F 分割成列输出:

1
2
3
4
5
awk -F, 'BEGIN {RS=":"} {print $2}' source-one-line.txt
one
two
three
four

4. ORS:输出记录分隔符

完成一个输出记录后由 ORS 进行分隔,一个输出记录 record 就是一行。 在 awk 中打印输出时,{print A, B, C} 相当于 3 个 field 组成一个 record 输出),即单独的 3 列组成 1 行。

直接看例子吧:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
➜ awk 'BEGIN {FS=","; ORS="\n------\n"} \ 
{print $1"\t"$2"\t"$3}' source.txt  | head -12
101	Ian Bicking	Mozilla
------
102	Hakim El Hattab	Whim
------
103	Paul Irish	Google
------
104	Addy Osmani	Google
------
105	Chris Wanstrath	Github
------
106	Mattt Thompson	Heroku
------

➜ awk 'BEGIN {FS=","; OFS="\t"; ORS="\n------\n"} \
{print $1,$2,$3}' source.txt
101	Ian Bicking	Mozilla
------
102	Hakim El Hattab	Whim
------
103	Paul Irish	Google
------
104	Addy Osmani	Google
------
105	Chris Wanstrath	Github
------
106	Mattt Thompson	Heroku
------
107	Ask Solem Hoel	VMware

➜ awk 'BEGIN {FS=","; OFS="\n"; ORS="\n------\n"} \
{print $1,$2,$3}' source.txt  | head -12
101
Ian Bicking
Mozilla
------
102
Hakim El Hattab
Whim
------
103
Paul Irish
Google
------

5. NR:记录的行号

NR 即 Number of Record,简单的理解就是行号的意思。表示 awk 当前处理的行的行号。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
➜ awk 'BEGIN {FS=","} {print "Id of record", NR, "is", $1}' source.txt
Id of record 1 is 101
Id of record 2 is 102
Id of record 3 is 103
Id of record 4 is 104
Id of record 5 is 105
Id of record 6 is 106
Id of record 7 is 107
➜ awk 'BEGIN {FS=","} {print "Id of record", NR, "is", $1} \ 
END {print "Total number of records is", NR}' source.txt
Id of record 1 is 101
Id of record 2 is 102
Id of record 3 is 103
Id of record 4 is 104
Id of record 5 is 105
Id of record 6 is 106
Id of record 7 is 107
Total number of records is 7

6. FILENAME 和 FNR

FILENAME 显示了当前文件 , FNR 关联到当前文件的记录行号。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
➜ awk -F, '{print "In file", FILENAME, ": record number", FNR, "is", $1} \
END {print "Toltal num of records is", NR}' source.txt source-multiple-fs.txt 
In file source.txt : record number 1 is 101
In file source.txt : record number 2 is 102
In file source.txt : record number 3 is 103
In file source.txt : record number 4 is 104
In file source.txt : record number 5 is 105
In file source.txt : record number 6 is 106
In file source.txt : record number 7 is 107
In file source-multiple-fs.txt : record number 1 is 101
In file source-multiple-fs.txt : record number 2 is 102
In file source-multiple-fs.txt : record number 3 is 103
In file source-multiple-fs.txt : record number 4 is 104
In file source-multiple-fs.txt : record number 5 is 105
In file source-multiple-fs.txt : record number 6 is 106
In file source-multiple-fs.txt : record number 7 is 107
Toltal num of records is 14

这里简单说一下 NR 和 FNR 的区别。

  • FNR: The ordinal number of the current record in the current file.
  • NR: The ordinal number of the current record from the start of input.

FNR 是当前文件里的行号,而 NR 是 输入 的行号。 区别在于处理多文件时,NR 相当于在计总的处理行号,而 FNR 永远只记录当前所处理的文件里的行号。看例子:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
➜ cat f1
line 1 in f1
line 2 in f1
line 3 in f1
line 4 in f1
➜ cat f2
line 1 in f2
line 2 in f2
line 3 in f2
➜ awk '{printf("%s: FNR - > %d NR -> %d: %s\n", FILENAME, FNR, NR, $0)}' f1 f2 
f1: FNR - > 1 NR -> 1: line 1 in f1
f1: FNR - > 2 NR -> 2: line 2 in f1
f1: FNR - > 3 NR -> 3: line 3 in f1
f1: FNR - > 4 NR -> 4: line 4 in f1
f2: FNR - > 1 NR -> 5: line 1 in f2
f2: FNR - > 2 NR -> 6: line 2 in f2
f2: FNR - > 3 NR -> 7: line 3 in f2

参考:Awk: different between NR and FNR

awk 变量

变量支持数字 , 字符和下划线

一个文件 source-star.txt 内容为:

101,Ian Bicking,Mozilla,1204

102,Hakim El Hattab,Whim,4029

103,Paul Irish,Google,7200

104,Addy Osmani,Google,2201

105,Chris Wanstrath,Github,1002

106,Mattt Thompson,Heroku,890

107,Ask Solem Hoel,VMware,2109

这个文件多加了最后一列 star 数 , 现在统计整个文件的 star:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
➜ awk -F, 'BEGIN {total=0} {print $2, "got",$4, "star"; total=total + $4} \
END {print "Total star is "total}'  source-star.txt
Ian Bicking got 1204 star
Hakim El Hattab got 4029 star
Paul Irish got 7200 star
Addy Osmani got 2201 star
Chris Wanstrath got 1002 star
Mattt Thompson got 890 star
Ask Solem Hoel got 2109 star
Total star is 18635

自增 / 减

使用 ++ 或者 --, 注意符号位置

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
➜ awk -F, '{print --$4}' source-star.txt 
1203
4028
7199
2200
1001
889
2108
➜ awk -F, '{print $4--}' source-star.txt
1204
4029
7200
2201
1002
890
2109
➜ awk -F, '{$4--; print $4}' source-star.txt
1203
4028
7199
2200
1001
889
2108

字符串操作

字符串直接 print 会连接起来 , 字符串相加会自动转化成数字相加

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
➜ awk 'BEGIN {
	FS=",";
    OFS=",";
    string1="GO";    
    string2="OGLE";    
    numberstring="100";
    string3=string1 string2;
    print "Concatenate string is:" string3;
    numberstring=numberstring+1;
    print "String to number:" numberstring;
}'
Concatenate string is:GOOGLE
String to number:101

复合运算

加减乘除和余数除计算,

文件 assignment.awk 内容如下:

BEGIN {
FS=“,”;
OFS=“,”;
total1 = total2 = total3 = total4 = total5 = 10;
total1 += 5; print total1;
total2 -= 5; print total2;
total3 *= 5; print total3;
total4 /= 5; print total4;
total5 %= 5; print total5;
}

1
2
3
4
5
6
➜ awk -f assignment.awk 
15
5
50
2
0

比较操作

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
# 只显示第 4 列小于 1500 的行
➜ awk -F, '$4 < 1500' source-star.txt
101,Ian Bicking,Mozilla,1204
105,Chris Wanstrath,Github,1002
106,Mattt Thompson,Heroku,890

➜ awk -F, '$1 == 103 {print $2}' source-star.txt
Paul Irish

# || 表示或者  && 表示和
➜ awk -F, '$4 >= 1000 && $4 <= 2000' source-star.txt 
101,Ian Bicking,Mozilla,1204
105,Chris Wanstrath,Github,1002
➜ awk -F, '$4 >= 1000 && $4 <= 2000 {print $0}' source-star.txt
101,Ian Bicking,Mozilla,1204
105,Chris Wanstrath,Github,1002

# star 少于 1000 或多于 5000 的项目的作者和对应 star 数
➜ awk -F, '$4 >= 5000 || $4 <= 1000 {print $2":"$4}' source-star.txt
Paul Irish:7200
Mattt Thompson:890

正则

~ 表示匹配 , !~ 表示不匹配

1
2
3
4
5
6
7
8
➜ awk -F, '$3 ~ "Github"' source.txt 
105,Chris Wanstrath,Github
➜ awk -F, '$3 !~ "Google"' source.txt
101,Ian Bicking,Mozilla
102,Hakim El Hattab,Whim
105,Chris Wanstrath,Github
106,Mattt Thompson,Heroku
107,Ask Solem Hoel,VMware

NF (number of fields) 是分割项数目 , $NF 表示最后一个分割项

1
2
3
# 计数 /etc/passwd 中以 /bin/zsh 作为最后一列的行
➜ awk -F: '$NF ~ /\/bin\/zsh/ {n++}; END {print n}' /etc/passwd
2

if 条件判断

if 条件判断语法为

1
2
3
4
5
6
7
if (conditional-expression)
    action
	
if (conditional-expression)
{
action1;
action2; }

例子:

1
2
3
# 和上面的一个例子一样
➜ awk -F "," '{ if ($3 ~ "Github") print $0}' source.txt
105,Chris Wanstrath,Github

if-else 语法为:

1
2
3
4
5
6
if (conditional-expression)
    action1
else
    action2
# or
conditional-expression ? action1 : action2 ;

例子:

1
2
3
4
5
6
# 奇数行就换行输出,偶数行直接在 `,` 后输出
➜ awk 'ORS = NR % 2?",":"\n"' source.txt
101,Ian Bicking,Mozilla,102,Hakim El Hattab,Whim
103,Paul Irish,Google,104,Addy Osmani,Google
105,Chris Wanstrath,Github,106,Mattt Thompson,Heroku
107,Ask Solem Hoel,VMware,

while

语法为:

1
2
while(condition)
    actions

例子,一个文件 while.awk 的内容如下:

1
2
3
4
5
6
7
8
{
    i=2; total=0;
    while (i <= NF) {
        total = total + $i;
        i++;
 }
    print "Item", $1, ":", total, "quantities sold";
}

将文件作为 awk 的输入文件:

1
2
3
4
5
6
➜ awk -f while.awk items-sold.txt 
Item 101 : 47 quantities sold
Item 102 : 10 quantities sold
Item 103 : 65 quantities sold
Item 104 : 20 quantities sold
Item 105 : 42 quantities sold

do-while

do-while 至少会执行一次,语法格式为:

1
2
3
do
	action
while(condition)

用 do-while 实现上一次的例子看看会得到什么结果,文件 dowhile.awk 内容为:

1
2
3
4
5
6
7
8
9
{
    i=2; total=0;
 do
 {
     total = total + $i;
     i++;
 } while (i <= NF)
 print "Item", $1, ":", total, "quantities sold";
}

将文件作为 awk 的输入文件:

1
2
3
4
5
6
➜ awk -f dowhile.awk items-sold.txt 
Item 101 : 47 quantities sold
Item 102 : 10 quantities sold
Item 103 : 65 quantities sold
Item 104 : 20 quantities sold
Item 105 : 42 quantities sold

for

for 循环语法格式:

1
2
for(initialization;condition;increment/decrement)
	actions

例子:

1
2
echo '1,2,3,4' | awk -F, '{for(i = 1; i <= NF; i++) total = total + i} END {print total}'
10

break continue exit

直接看例子:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
# 程序一直运行打印 Iteration, 并且累加 x, 直到 x 等于 10 停止程序-break
➜ awk 'BEGIN{
x=1;
while(1)
	{
	print "Iteration";
	if ( x==10 )
		break;
		x++;
	}
}'
Iteration
Iteration
Iteration
Iteration
Iteration
Iteration
Iteration
Iteration
Iteration
Iteration

# x 从 1 到 10, 如果 x 等于 5 直接直接累加 x 而不打印
➜ awk 'BEGIN{
	x=1;
	while(x<=10){
		if(x==5){
			x++;
			continue;
		}
	print "Value of x",x;x++;
	}
}'
Value of x 1
Value of x 2
Value of x 3
Value of x 4
Value of x 6
Value of x 7
Value of x 8
Value of x 9
Value of x 10
# x 从 1 到 10, 当 x 等于 5 的时候程序直接退出
➜ awk 'BEGIN{
	x=1;
	while(x<=10){
		if(x==5){
			exit;
		}
print "Value of x",x;x++;
	}
}'
Value of x 1
Value of x 2
Value of x 3
Value of x 4

关联数组

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# awk 的关联数组中 item[101] 和 item["101"] 意义一样
➜ awk 'BEGIN { item[101]="Github"; print item["101"]}' 
Github

# 可以用 in 检验是否包含本项
➜ awk 'BEGIN { item[101]="a"; if ( 101 in item ) print "Has 101"}'
Has 101
# 还可以使用 for 循环读取列表
➜ awk 'BEGIN {
			item[101]="Github";
			item[21]="Google";
			for (x in item)
        		print item[x]}'
Google
Github

# 多维数组 , delete 可以删除元素 .PS item[2,1] 这样的格式有问题
# 因为会被翻译成 2#2("2\0342"), 假设要设置分隔符可以使用 SUBSEP=",";
➜ awk 'BEGIN {item["1,1"]="Github"; item["1,2"]="Google"; \
		item["2,1"]="Whim"; delete item["2,1"];
		for (x in item)
			print "Index",x,"contains",item[x]}'
Index 1,1 contains Github
Index 1,2 contains Google

格式化打印

\n 是换行,\t 是 tab,\v 是垂直 tab,%s 字符串 ; %c 单个字符 ; %d 数字 ; %f 浮点数

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
➜ awk 'BEGIN {printf "Field 1\t\tField 2\tField 3\tField 4\n" }' 
Field 1		Field 2	Field 3	Field 4
➜ awk 'BEGIN {printf "Field 1\t\tField 2\t\tField 3\tField 4\n" }'
Field 1		Field 2		Field 3	Field 4

➜ awk 'BEGIN {printf "Field 1\vField 2\vField 3\vField 4\n"}'  
Field 1
       Field 2
              Field 3
                     Field 4

➜ cat printf-width.awk 
BEGIN {
	FS=","
	printf "%3 s\t%10 s\t%10 s\t%5 s\t%3 s\n",
    "Num","Description","Type","Price","Qty"
	printf "-----------------------------------------------------\n"
}
{
    printf "%3d\t%10 s\t%10 s\t%g\t%d\n", $1,$2,$3,$4,$5
}

➜ awk -f printf-width.awk items.txt 
Num	Description	      Type	Price	Qty
-----------------------------------------------------
101	HD Camcorder	     Video	210	10
102	Refrigerator	 Appliance	850	2
103	MP3 Player	         Audio	270	15
104	Tennis Racket	    Sports	190	20
105	Laser Printer	    Office	475	5

内置函数

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
# int - 将数字转换成整形 , 类似的函数还有 sqrt, sin, cos...
➜ awk 'BEGIN {print int(4.1);print int(-6.22);print int(strings)}'
4
-6
0

# rand - 随机 0-1 的数字 ; srand -初始化随机数的初始值
➜ cat srand.awk 
BEGIN {
    srand(5);
    count=0;
    max=30;
    while (count < 5) {
        # 随机数范围为 5-30
        rnd = int(rand() * max);
        print rnd;
        count++;
    }
}

➜ awk -f strand.awk 
19
9
21
8
13

# index - 所查字符在字符串中的位置 , 没找到会返回 0
➜ awk 'BEGIN{str="This is a test"; print index(str, "a"); print index(str, "y")}'
9
0
# length - 字符串的长度
➜ awk -F, '{print length($0)}' source.txt
23
24
21
22
26
25
25
# split - 分片 PS: 使用 awk 分片的顺序有问题 ;
# split 第一个参数是要分割的内容 , 第二个是分割后的结果保存的数组 , 第三个是使用的分隔符echo "101 arg1:arg2:arg3" | awk '{split($2,out,":"); for (x in out) print out[x]}'
arg1
arg2
arg3
# substr - 取字符串范围内容 ;
# 第一个参数是要取的内容 , 第二个是开始位置 (从 1 开始), 第三个是要取的长度echo "This is test"|awk '{print substr($3,2,2);}'
es
# sub - 替换原来的字符串 , 但是只替换第一个符合项 ; gsub - 替换全部选择项
➜ awk 'BEGIN{str="ThIs is test"; sub("[Ii]s","e", str); print str;}' 
The is test
➜ awk 'BEGIN{str="ThIs is test"; gsub("[Ii]s","e", str); print str;}'
The e test
# match - 返回某子字符串是否匹配了某字符串 ;
# RSTART - awk 自带变量,返回匹配的开始位置
# RLENGTH - awk 自带变量,返回匹配串的长度
➜ awk 'BEGIN{str="This is test"; if (match(str, "test")) {print substr(str,RSTART,RLENGTH)}}'  
# tolower/toupper - 把字符串都变成小写 / 大写
➜ awk 'BEGIN{str="This is test"; print tolower(str); print toupper(str);}'
this is test
THIS IS TEST

# ARGC - 参数的数量 ; ARGV 参数的数组
➜ cat arguments.awk
BEGIN {
    print "ARGC=",ARGC
    for (i = 0; i < ARGC; i++)
  print ARGV[i]
}
➜ awk -f arguments.awk 
ARGC= 1
awk
➜ awk -f arguments.awk source.txt 
ARGC= 2
awk
source.txt
➜ awk -f arguments.awk source.txt source-star.txt 
ARGC= 3
awk
source.txt
source-star.txt

内置变量

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# ENVIRON - 系统环境变量
➜ cat environ.awk
BEGIN {
 OFS="="
 for(x in ENVIRON)
     print x,ENVIRON[x];
}
➜ awk -f environ.awk 
SHLVL=1
DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/1000/bus
UPDATE_ZSH_DAYS=13
XDG_SESSION_PATH=/org/freedesktop/DisplayManager/Session0
PWD=/home/adam/Learning/sed&awk
GDMSESSION=lightdm-xsession
XDG_CONFIG_DIRS=/etc/xdg
XDG_CURRENT_DESKTOP=XFCE
JAVA_HOME=/usr/lib/jvm/oracle-java8-jdk-amd64/jre
XDG_GREETER_DATA_DIR=/var/lib/lightdm/data/adam
XDG_DATA_DIRS=/usr/share/xfce4:/usr/local/share/:/usr/share/:/usr/share
ZSH=/home/adam/.oh-my-zsh
SHELL=/bin/zsh
ALLOW_WGCNA_THREADS=4
QT_LINUX_ACCESSIBILITY_ALWAYS_ON=1
COLORTERM=truecolor
(.... 部分省略 .....)

# IGNORECASE - 设置为 1 忽略大小写
➜ awk 'BEGIN{IGNORECASE=1} /github/{print}' source.txt
105,Chris Wanstrath,Github

自定义函数

自定义一个函数写入文件 function-debug.awk

1
2
3
4
5
6
7
function mydebug (message) {
    print ("Debug Time:" strftime("%a %b %d %H:%M:%S %Z %Y", systime()))
    print (message)
}
{
    mydebug($NF)
}

然后调用这个函数:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
➜ awk -f function-debug.awk source.txt
Debug Time:Sat May 20 20:56:40 HKT 2017
Bicking,Mozilla
Debug Time:Sat May 20 20:56:40 HKT 2017
Hattab,Whim
Debug Time:Sat May 20 20:56:40 HKT 2017
Irish,Google
Debug Time:Sat May 20 20:56:40 HKT 2017
Osmani,Google
Debug Time:Sat May 20 20:56:40 HKT 2017
Wanstrath,Github
Debug Time:Sat May 20 20:56:40 HKT 2017
Thompson,Heroku
Debug Time:Sat May 20 20:56:40 HKT 2017
Hoel,VMware

系统调用

使用 system 函数可以调用 shell 命令:

1
2
3
4
5
➜ awk 'BEGIN {system("date")}' 
Sat May 20 20:58:54 HKT 2017
# systime 和 strftime 上面见过,处理时间和格式化时间
➜ awk 'BEGIN {print strftime("%c",systime())}' 
Sat 20 May 2017 09:04:12 PM HKT