JackieMium / my_blog

在 Issues 中建立的个人博客
GNU General Public License v3.0
31 stars 12 forks source link

sed 和 awk 学习 #8

Open JackieMium opened 6 years ago

JackieMium commented 6 years ago
2017-05-20

sed 、awk 和 grep 并称 Linux 系统下文本处理三剑客,三者都是非交互式的文本编辑器。

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

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

sed

语法1

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

系统里/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
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
......(省略)......

例子:

# -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

语法2

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

例子:

# 打印以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}

例子:

# 打印以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

语法4

格式:

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

例子:

# 打印以root开头或者sync结尾的行
sed -n '{
/^root/ p
/nobody$/ p
}' /etc/passwd
root:x:0:0:root:/root:/bin/zsh
sync:x:4:65534:sync:/bin:/bin/sync

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

范围

➜ sed -n '1~2 p' source.txt
# 从第1行开始,步长为2, 及奇数行
101,Ian Bicking,Mozilla
103,Paul Irish,Google
105,Chris Wanstrath,Github
107,Ask Solem Hoel,VMware                                                                                                                ➜ sed -n '2~3 p' source.txt
# 从第2行开始,步长为3
102,Hakim El Hattab,Whim
105,Chris Wanstrath,Github

模式匹配

# 寻找包含Paul的行
➜ sed -n '/Paul/ p' source.txt
103,Paul Irish,Google
# 在第一行开始到第五行中, 从找到Paul开始打印到第五行
➜ 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

删除行

# 删除所有行
➜ sed 'd' source.txt
(无输出)
# 只删除第二行
➜ sed '2 d' source.txt 
...
# 删除第一到第四行
➜ 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

重定向

# 将source.txt内容重定向写到output.txt
➜ sed 'w output.txt' source.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'

替换

格式为:

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

例子:

# 替换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 '1s/a/A/'  source.txt|head -1
101,IAn Bicking,Mozilla
# g可以替换每行的全部符合
➜ sed '1s/a/A/g'  source.txt|head -1
101,IAn Bicking,MozillA
# 可以直接指定想要替换的第N个匹配项,这里是第二个
➜ sed '1s/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忽略匹配的大小写,看来freebsd的不能用
➜ sed '1s/iaN/IAN/i'  source.txt|head -1
101,IAN Bicking,Mozilla

➜ cat files.txt 
/etc/passwd
/etc/group
# 给每行前和后都添加点字符
➜ sed 's/\(.*\)/ls -l \1|head -1/' files.txt
ls -l /etc/passwd|head -1
ls -l /etc/group|head -1
# 用sed执行这个字符串命令
➜ 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

# sed分隔符不只可以使用'/'
$sed 's|/usr/local/bin|/usr/bin|' path.txt
$sed 's^/usr/local/bin^/usr/bin^' path.txt
$sed 's@/usr/local/bin@/usr/bin@' path.txt
$sed 's!/usr/local/bin!/usr/bin!' path.txt

替换覆盖

➜ 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

& 代表匹配到的内容

➜ 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>>

正则

# ^表示匹配以什么开头
➜ 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                         
# 转化windows文件到unix格式
➜ sed 's/.$//' filename                             

#\1表示第一个正则匹配到的数据
➜ sed 's/\([^,]*\).*/\1/g' source.txt |head -1
101
#给每个单词第一个字母加括号
➜ echo "Dong Wei Ming" | sed 's/\(\b[A-Z]\)/\(\1\)/g'
(D)ong (W)ei (M)ing
➜ sed 's/\(^\|[^0-9.]\)\([0-9]\+\)\([0-9]\{3\}\)/\1\2,\3/g' numbers.txt
1
12
123
1,234
12,345
123,456
# 只取第一和第三列,并且换了他们的位置
$sed 's/\([^,]*\),\([^,]*\),\([^,]*\).*/\3,\1/g' source.txt
Mozilla,101
Whim,102
Google,103
Google,104
Github,105
Heroku,106
VMware,107

其他

# \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
# 使用以上功能:调换前2列,把名字列全部大写,公司列全部小写
➜ sed 's/\([^,]*\),\([^,]*\),\(.*\).*/\U\2\E,\1,\L\3/g' source.txt
IAN BICKING,101,mozilla
HAKIM EL HATTAB,102,whim
PAUL IRISH,103,google
ADDY OSMANI,104,google
CHRIS WANSTRATH,105,github
MATTT THOMPSON,106,heroku
ASK SOLEM HOEL,107,vmware

sed 可执行脚本

➜ 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 修改源文件和备份

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

行后增加和行前插入

语法格式:

例子:

➜ 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

例子:

# 修改含有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

其他用法:

# = 可以显示行号
➜ 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

awk -Fs '/pattern/ {action}' input-file
#or
awk -Fs '{action}' intput-file
# -F表示设置分隔符,不指定就是默认为空字符, Fs即field seperator

例子:

# 用:分割,查找匹配systemd的行并且打印冒号分割后的第一部分
➜ awk -F: '/systemd/ {print $1}' /etc/passwd
systemd-timesync
systemd-network
systemd-resolve
systemd-bus-proxy

awk数据结构

# 1 BEGIN { awk-commands } 在执行awk body之前执行这个awk-commands,而且只一次
# 2 /pattern/ {action} body部分,也就是awk要执行的主体,比如十行,那么这个主体就调用10次
# 3 END { awk-commands } 在执行完body之后执行,也是只一次
➜ awk -F: 'BEGIN {print "----header----"} /systemd/ {print $1} \
END {print "----footer----"}' /etc/passwd
----header----
systemd-timesync
systemd-network
systemd-resolve
systemd-bus-proxy
----footer----
# 当然可以只有其中一种或者集中数据结构
➜ awk -F: 'BEGIN {print "UID"} {print $3}' /etc/passwd | head -3
UID
0
1
➜ awk 'BEGIN {print "Hello World!"}'
Hello World!

print

# 默认print就是打印文件全文到终端
➜ 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
# 下面是通过,分割, 输出第二段。$0表示全行,类似shell用法
$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
# 一个格式化更好看些的效果
➜ 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 ',' '/Whim/ {print $2, $3}' source.txt
Hakim El Hattab Whim
# 可以加点格式化语句
➜ awk -F, '/Whim/ {print "Whim\"s full name:",$2}' source.txt
Whim"s full name: Hakim El Hattab

awk内置变量

1. FS:输入字段分隔符

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

➜ 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

➜ 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 分割输出:

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输出)。

直接看例子吧:

➜ awk 'BEGIN {FS=","; OFS="\n"; ORS="\n------\n"} \
{print $1"\t"$2"\t"$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:记录的数目

➜ 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关联到当前文件的记录数。

➜ 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

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:

➜ 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

自增/减

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

➜ 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 会连接起来, 字符串相加会自动转化成数字相加

➜ 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;

}

➜ awk -f assignment.awk 
15
5
50
2
0

比较操作

# 只会显示小于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

正则

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

➜ 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表示最后一个分割项

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

if 条件判断

if 条件判断语法为

if (conditional-expression)
    action

if (conditional-expression)
{
action1;
action2; }

例子:

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

if-else语法为:

if (conditional-expression)
    action1
else
    action2
# or
conditional-expression ? action1 : action2 ;

例子:

# 奇数行就换行输出,偶数行直接在,后输出
➜ 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

语法为:

while(condition)
    actions

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

while.awk 
{
    i=2; total=0;
    while (i <= NF) {
        total = total + $i;
        i++;
 }
    print "Item", $1, ":", total, "quantities sold";
}

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

➜ 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 至少会执行一次,语法格式为:

do
    action
while(condition)

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

{
    i=2; total=0;
 do
 {
     total = total + $i;
     i++;
 } while (i <= NF)
 print "Item", $1, ":", total, "quantities sold";
}

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

➜ 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 循环语法格式:

for(initialization;condition;increment/decrement)
    actions

例子:

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

break continue exit

直接看例子:

# 程序一直运行打印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

关联数组

# 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是换行
Line 1
Line 2
# \t是tab
➜ 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
# \v是垂直tab
➜ awk 'BEGIN {printf "Field 1\vField 2\vField 3\vField 4\n"}'  
Field 1
       Field 2
              Field 3
                     Field 4

# %s字符串; %c单个字符; %d数字; %f浮点数......
➜ cat printf-width.awk 
BEGIN {
    FS=","
    printf "%3s\t%10s\t%10s\t%5s\t%3s\n",
    "Num","Description","Type","Price","Qty"
    printf "-----------------------------------------------------\n"
}
{
    printf "%3d\t%10s\t%10s\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

内置函数

# 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++;
    }
}
# 使用osx的awk随机范围不对
➜ 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

内置变量

# 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

function mydebug (message) {
    print ("Debug Time:" strftime("%a %b %d %H:%M:%S %Z %Y", systime()))
    print (message)
}
{
    mydebug($NF)
}
# 函数的位置不重要

然后调用这个函数:

➜ 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 命令:

➜ 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

awk 高级话题

getline

直接看例子:

# awk首先读入一行,接着处理 getline 函数再获得一行....所以最后print得到的就是所有奇数行
➜ awk -F, '{print $0;getline;}' source.txt  
101,Ian Bicking,Mozilla
103,Paul Irish,Google
105,Chris Wanstrath,Github
107,Ask Solem Hoel,VMware
105,Chris Wanstrath,Github
107,Ask Solem Hoel,VMware
# 我们使用getline 并把这行变量赋值给tmp,这个例子清晰的显示出了上一例的处理过程
➜ awk -F, '{getline tmp; print "$0->", $0; print "tmp->", tmp}' source.txt 
$0-> 101,Ian Bicking,Mozilla
tmp-> 102,Hakim El Hattab,Whim
$0-> 103,Paul Irish,Google
tmp-> 104,Addy Osmani,Google
$0-> 105,Chris Wanstrath,Github
tmp-> 106,Mattt Thompson,Heroku
$0-> 107,Ask Solem Hoel,VMware
tmp-> 106,Mattt Thompson,Heroku
# 执行外部程序, close可关闭管道,比如这里必须是`|getline`之前的命令
➜ awk 'BEGIN{"date"| getline;close("date");print "Timestamp:" $0}'
Timestamp:Sat May 20 21:12:30 HKT 2017
# or
➜ awk 'BEGIN{"date"| getline timestamp;close("date");print "Timestamp:" timestamp}'
Timestamp:Sat May 20 21:13:41 HKT 2017
# 这个例子不是很懂...