grep,sed,awk

27 Jul 2021

元字符

? 字符

?字符代表单个字符。

 # 存在文件 a.txt 和 b.txt
 $ ls ?.txt
 a.txt b.txt

上面命令中,?表示单个字符,所以会同时匹配a.txtb.txt

如果匹配多个字符,就需要多个?连用。

 # 存在文件 a.txt、b.txt 和 ab.txt
 $ ls ??.txt
 ab.txt

上面命令中,??匹配了两个字符。

注意,?不能匹配空字符。也就是说,它占据的位置必须有字符存在。

* 字符

*代表任意数量的字符。

 # 存在文件 a.txt、b.txt 和 ab.txt
 $ ls *.txt
 a.txt b.txt ab.txt
 
 # 输出所有文件
 $ ls *

上面代码中,*匹配任意长度的字符。

*可以匹配空字符。

 # 存在文件 a.txt、b.txt 和 ab.txt
 $ ls a*.txt
 a.txt ab.txt

[…] 模式

[...]匹配方括号之中的任意一个字符,比如[aeiou]可以匹配五个元音字母。

 # 存在文件 a.txt 和 b.txt
 $ ls [ab].txt
 a.txt b.txt
 
 $ ls *[ab].txt
 ab.txt a.txt b.txt

[start-end]表示一个连续的范围。

 # 存在文件 a.txt、b.txt 和 c.txt
 $ ls [a-c].txt
 a.txt b.txt c.txt
 
 # 存在文件 report1.txt、report2.txt 和 report3.txt
 $ ls report[0-9].txt
 report1.txt report2.txt report3.txt

[^...][!...]

[^...][!...]表示匹配不在方括号里面的字符(不包括空字符)。这两种写法是等价的。

 # 存在文件 a.txt、b.txt 和 c.txt
 $ ls [^a].txt
 b.txt c.txt

这种模式下也可以使用连续范围的写法[!start-end]

 $ echo report[!1-3].txt
 report4.txt report5.txt

上面代码中,[!1-3]表示排除1、2和3。

{…} 模式

{...} 表示匹配大括号里面的所有模式,模式之间使用逗号分隔。

 $ echo d{a,e,i,u,o}g
 dag deg dig dug dog

它可以用于多字符的模式。

 $ echo {cat,dog}
 cat dog

{...}[...]有一个很重要的区别。如果匹配的文件不存在,[...]会失去模式的功能,变成一个单纯的字符串,而{...}依然可以展开。

 # 不存在 a.txt 和 b.txt
 $ ls [ab].txt
 ls: [ab].txt: No such file or directory
 
 $ ls {a,b}.txt
 ls: a.txt: No such file or directory
 ls: b.txt: No such file or directory

上面代码中,如果不存在a.txtb.txt,那么[ab].txt就会变成一个普通的文件名,而{a,b}.txt可以照样展开。

大括号可以嵌套。

 $ echo {j{p,pe}g,png}
 jpg jpeg png

大括号也可以与其他模式联用。

 $ echo {cat,d*}
 cat dawg dg dig dog doug dug

上面代码中,会先进行大括号扩展,然后进行*扩展。

{start..end} 模式

{start..end}会匹配连续范围的字符。

 $ echo d{a..d}g
 dag dbg dcg ddg
 
 $ echo {11..15}
 11 12 13 14 15

如果遇到无法解释的扩展,模式会原样输出。

 $ echo {a1..3c}
 {a1..3c}

这种模式与逗号联用,可以写出复杂的模式。

 $ echo .{mp{3..4},m4{a,b,p,v}}
 .mp3 .mp4 .m4a .m4b .m4p .m4v

注意点

通配符有一些使用注意点,不可不知。

(1)通配符是先解释,再执行。

Bash 接收到命令以后,发现里面有通配符,会进行通配符扩展,然后再执行命令。

 $ ls a*.txt
 ab.txt

上面命令的执行过程是,Bash 先将a*.txt扩展成ab.txt,然后再执行ls ab.txt

(2)通配符不匹配,会原样输出。

Bash 扩展通配符的时候,发现不存在匹配的文件,会将通配符原样输出。

 # 不存在 r 开头的文件名
 $ echo r*
 r*

上面代码中,由于不存在r开头的文件名,r*会原样输出。

下面是另一个例子。

 $ ls *.csv
 ls: *.csv: No such file or directory

另外,前面已经说过,这条规则对{...}不适用

(3)只适用于单层路径。

上面所有通配符只匹配单层路径,不能跨目录匹配,即无法匹配子目录里面的文件。或者说,?*这样的通配符,不能匹配路径分隔符(/)。

如果要匹配子目录里面的文件,可以写成下面这样。

 $ ls */*.txt

(4)可用于文件名。

Bash 允许文件名使用通配符。这时,引用文件名的时候,需要把文件名放在单引号里面。

 $ touch 'fo*'
 $ ls
 fo*

上面代码创建了一个fo*文件,这时*就是文件名的一部分。

文本内容的查找grep

* grep 选项 文本文件1 [ ... 文本文件n ]  * 常用选项
   	* -i 忽略大小写
   	* -r 递归读取每一个目录下的所有文件

sed –> stream editor for filtering and transforming text

sed 一般用于对文本内容做替换

sed 的替换命令

全局替换

标志位

# /2只替换第二次出现的模式head -5 /etc/passwd | sed 's/root/!!!!/2'
root:x:0:0:!!!!:/root:/bin/zsh
bin:x:1:1:bin:/bin:/sbin/nologin
daemon:x:2:2:daemon:/sbin:/sbin/nologin
adm:x:3:4:adm:/var/adm:/sbin/nologin
lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin

➜   # p 输出匹配的行替换之后的结果head -5 /etc/passwd | sed 's/root/!!!!/p'
!!!!:x:0:0:root:/root:/bin/zsh
!!!!:x:0:0:root:/root:/bin/zsh
bin:x:1:1:bin:/bin:/sbin/nologin
daemon:x:2:2:daemon:/sbin:/sbin/nologin
adm:x:3:4:adm:/var/adm:/sbin/nologin
lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin

➜   # -n 阻止默认输出,只输出匹配的那一行head -5 /etc/passwd | sed -n 's/root/!!!!/p'
!!!!:x:0:0:root:/root:/bin/zsh

➜   # w file 将模式空间的内容写入到文件
# 仅仅输出匹配的那一行head -5 /etc/passwd | sed -n 's/root/!!!!/w /tmp/a.txt'cat /tmp/a.txt
!!!!:x:0:0:root:/root:/bin/zsh

寻址

默认对每行进行操作,增加寻址后对匹配的行进行操作

head -6 /etc/passwd
root:x:0:0:root:/root:/bin/zsh
bin:x:1:1:bin:/bin:/sbin/nologin
daemon:x:2:2:daemon:/sbin:/sbin/nologin
adm:x:3:4:adm:/var/adm:/sbin/nologin
lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin
sync:x:5:0:sync:/sbin:/bin/sync
# 默认匹配全文head -6 /etc/passwd | sed 's/adm/!/'
root:x:0:0:root:/root:/bin/zsh
bin:x:1:1:bin:/bin:/sbin/nologin
daemon:x:2:2:daemon:/sbin:/sbin/nologin
!:x:3:4:adm:/var/adm:/sbin/nologin
lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin
sync:x:5:0:sync:/sbin:/bin/sync
# 只匹配第一行head -6 /etc/passwd | sed '1s/adm/!/'
root:x:0:0:root:/root:/bin/zsh
bin:x:1:1:bin:/bin:/sbin/nologin
daemon:x:2:2:daemon:/sbin:/sbin/nologin
adm:x:3:4:adm:/var/adm:/sbin/nologin
lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin
sync:x:5:0:sync:/sbin:/bin/sync
# 匹配1~4行head -6 /etc/passwd | sed '1,4s/adm/!/'
root:x:0:0:root:/root:/bin/zsh
bin:x:1:1:bin:/bin:/sbin/nologin
daemon:x:2:2:daemon:/sbin:/sbin/nologin
!:x:3:4:adm:/var/adm:/sbin/nologin
lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin
sync:x:5:0:sync:/sbin:/bin/sync
# 匹配1到最后一行$head -6 /etc/passwd | sed '1,$s/adm/!/'
root:x:0:0:root:/root:/bin/zsh
bin:x:1:1:bin:/bin:/sbin/nologin
daemon:x:2:2:daemon:/sbin:/sbin/nologin
!:x:3:4:adm:/var/adm:/sbin/nologin
lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin
sync:x:5:0:sync:/sbin:/bin/sync
➜   # 使用正则寻址,包含adm的行才进行替换head -6 /etc/passwd | sed '/adm/s/adm/!/'
root:x:0:0:root:/root:/bin/zsh
bin:x:1:1:bin:/bin:/sbin/nologin
daemon:x:2:2:daemon:/sbin:/sbin/nologin
!:x:3:4:adm:/var/adm:/sbin/nologin
lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin
sync:x:5:0:sync:/sbin:/bin/sync

➜   # 正则和行号混用
# bin开头的行到最后一行head -6 /etc/passwd | sed '/^bin/,$s/nologin/!/'
root:x:0:0:root:/root:/bin/zsh
bin:x:1:1:bin:/bin:/sbin/!
daemon:x:2:2:daemon:/sbin:/sbin/!
adm:x:3:4:adm:/var/adm:/sbin/!
lp:x:4:7:lp:/var/spool/lpd:/sbin/!
sync:x:5:0:sync:/sbin:/bin/sync

分组

head -6 /etc/passwd
root:x:0:0:root:/root:/bin/zsh
bin:x:1:1:bin:/bin:/sbin/nologin
daemon:x:2:2:daemon:/sbin:/sbin/nologin
adm:x:3:4:adm:/var/adm:/sbin/nologin
lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin
sync:x:5:0:sync:/sbin:/bin/sync
# 寻址bin开头的那行,进行多个模式匹配替换head -6 /etc/passwd | sed '/^bin/{s/bin/nib/2;s/nologin/ohmy/}'
root:x:0:0:root:/root:/bin/zsh
bin:x:1:1:nib:/bin:/sbin/ohmy
daemon:x:2:2:daemon:/sbin:/sbin/nologin
adm:x:3:4:adm:/var/adm:/sbin/nologin
lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin
sync:x:5:0:sync:/sbin:/bin/sync

脚本文件

* 可以将选项保存为文件,使用-f 加载脚本文件 
* sed -f sedscript filename
cat sed.s
/^bin/{s/bin/nib/2;s/nologin/ohmy/}
# 使用保存到文件中的sed指令,文件中sed指令不需要用''包起来head -6 /etc/passwd | sed -f sed.s
root:x:0:0:root:/root:/bin/zsh
bin:x:1:1:nib:/bin:/sbin/ohmy
daemon:x:2:2:daemon:/sbin:/sbin/nologin
adm:x:3:4:adm:/var/adm:/sbin/nologin
lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin
sync:x:5:0:sync:/sbin:/bin/sync

删除命令

cat bfile
b
a
aa
aaa
abb
baa
abbbb
# 删除保护ab的行sed '/ab/d' bfile
b
a
aa
aaa
baa
# 使用/d后面的命令不会在背删除行执行
# 相当于先全局删除包含ab的行,然后再执行s/a/!/sed '/ab/d;s/a/!/' bfile
b
!
!a
!aa
b!a
# 打印内容和行号sed '/ab/d;=' bfile
1
b
2
a
3
aa
4
aaa
6
baa

追加插入和更改

cat bfile
b
a
aa
aaa
abb
baa
abbbb
# a 追加,在匹配的行后面插入sed '/ab/a hello' bfile
b
a
aa
aaa
abb
hello
baa
abbbb
hello
# i 插入,在匹配的行前面插入sed '/ab/i hello' bfile
b
a
aa
aaa
hello
abb
baa
hello
abbbb
# c 变更,把匹配的行替换成 对应的字符串sed '/ab/c hello' bfile
b
a
aa
aaa
hello
baa
hello

读文件和写文件

# r read, 用文件中的内容替换匹配的行sed '/ab/r afile' bfile
b
a
aa
aaa
abb
cc a a
baa
abbbb
cc a a

# 把包含ab的行写入文件 dfilesed '/ab/w dfile' bfile
b
a
aa
aaa
abb
baa
abbbb
➜  cat dfile
abb
abbbb

打印

# p打印匹配的行内容, -n抑制默认输出sed -n '/ab/p' bfile
abb
abbbb
# 等价于 grepgrep ab bfile
abb
abbbb

下一行

# 打印包含ab的行号sed  '/ab/=' bfile
b
a
aa
aaa
5
abb
baa
7
abbbb

退出命令

seq 1 10000000> lines.txt
➜   ll
total 76M
-rw-r--r-- 1 root root 76M Jul 24 16:55 lines.txt
# 遍历整个文件,打印前10行,效率低time sed -n '1,10p' lines.txt
1
2
3
4
5
6
7
8
9
10
sed -n '1,10p' lines.txt  0.35s user 0.02s system 110% cpu 0.327 total
# 到文件第10行退出
➜  sed_practice time sed '10q' lines.txt
1
2
3
4
5
6
7
8
9
10
sed '10q' lines.txt  0.00s user 0.00s system 91% cpu 0.006 total

sed 的多行模式

多行匹配命令

# 生成测试文件cat > b.txt << EOF
heredoc> hell
heredoc> o bash hel
heredoc> lo bash
heredoc> EOF
➜  cat b.txt
hell
o bash hel
lo bash

➜  sed 'N;s/\n//;s/hello bash/hello sed\n/;P;D' b.txt
hello sed
 hello sed

sed 的保持空间

AWK – pattern scanning and processing language

awk 一般用于对文本内容进行统计、按需要的格式进行输出

AWK 和 sed 的区别

AWK 脚本的流程控制

AWK 的字段引用和分离

# 寻找文件中以menu开头的行,并打印整行awk '/^menu/{print $0}' /boot/grub2/grub.cfg
menuentry 'CentOS Linux (3.10.0-957.el7.x86_64) 7 (Core)' --class centos --class gnu-linux --class gnu --class os --unrestricted $menuentry_id_option 'gnulinux-3.10.0-957.el7.x86_64-advanced-059e318c-0a80-4b87-9dfb-ed74aeb2afd5' {
menuentry 'CentOS Linux (0-rescue-0d9de9ab17324e10b3c3fb7a80f6e95e) 7 (Core)' --class centos --class gnu-linux --class gnu --class os --unrestricted $menuentry_id_option 'gnulinux-0-rescue-0d9de9ab17324e10b3c3fb7a80f6e95e-advanced-059e318c-0a80-4b87-9dfb-ed74aeb2afd5' {
# 寻找文件中以menu开头的行,以'为分隔符,打印第二个字段awk -F "'" '/^menu/{print $2}' /boot/grub2/grub.cfg
CentOS Linux (3.10.0-957.el7.x86_64) 7 (Core)
CentOS Linux (0-rescue-0d9de9ab17324e10b3c3fb7a80f6e95e) 7 (Core)
# 寻找文件中以menu开头的行,以'为分隔符,打印第一个字段awk -F "'" '/^menu/{print $1}' /boot/grub2/grub.cfg
menuentry
menuentry
➜   # {print x++,$2} 打印的时候x自增,显示行号awk -F "'" '/^menu/{print x++,$2}' /boot/grub2/grub.cfg
0 CentOS Linux (3.10.0-957.el7.x86_64) 7 (Core)
1 CentOS Linux (0-rescue-0d9de9ab17324e10b3c3fb7a80f6e95e) 7 (Core)

AWK 的表达式

AWK 的条件和循环