Shell脚本学习指南-第三章-查找与替换
第3章 查找与替换
3.1 grep 查找文本
三个历史版本
| 旧命令 | 等价POSIX写法 | 说明 |
|---|---|---|
| grep | grep |
BRE基础正则 |
| egrep | grep -E |
ERE扩展正则 |
| fgrep | grep -F |
纯字面字符串,不解析正则 |
POSIX标准推荐使用选项形式,不建议直接写egrep/fgrep,但现实环境依旧兼容。
grep常用选项
| 选项 | 作用 |
|---|---|
-E |
使用ERE扩展正则,替代egrep |
-F |
固定字符串匹配,不解析正则,替代fgrep |
-e pattern |
指定模式;模式以-开头必须用‑e |
-f file |
从文件读取多条匹配模式 |
-i |
忽略大小写 |
-l |
只打印匹配的文件名,不打印匹配行 |
-q |
静默模式,只看退出状态,无输出;脚本判断用,屏幕什么都不打印,只返回成功 / 失败状态。 |
-s |
屏蔽错误,常和‑q一起用 |
-v |
反向匹配:输出不匹配的行 |
示例
3.2 正则表达式
两套标准:
- BRE基础正则:
grep默认、sed、ed;很多符号需要\开启特殊能力- ERE扩展正则:
grep -E、awk;() {} + ? |直接写,不用反斜杠
BRE与ERE元字符对照表
| 符号 | BRE(grep默认) | ERE(grep -E) | 含义 |
|---|---|---|---|
\ |
✓ | ✓ | 转义符;消除元字符特殊含义;BRE给普通符号加\激活特殊能力 |
. |
✓ | ✓ | 匹配任意1个字符(不匹配NUL) |
* |
✓ | ✓ | 前面单元重复0次或多次 |
^ |
✓ | ✓ | 行开头锚点;BRE仅在正则开头生效 |
$ |
✓ | ✓ | 行结尾锚点;BRE仅在正则末尾生效 |
[...] |
✓ | ✓ | 字符集,匹配括号中任意一个字符;[^...]取反 |
\{n,m\} |
✓ | — | 次数区间,必须带反斜杠 |
\( \) |
✓ | — | 捕获分组,反向引用\1,\(内容\) 叫捕获分组:把括号里面匹配到的文本 “抓起来、存起来”。\1、\2…\9 叫做反向引用:把刚才抓出来的文本,再拿出来复用。 |
\1~\9 |
✓ | — | 反向引用;ERE不支持反向引用 |
{n,m} |
— | ✓ | 次数区间,直接写,不需要\ |
+ |
— | ✓ | 前面单元至少1次(1次或多次) |
? |
— | ✓ | 前面单元0次或1次,可选 |
| ` | ` | — | ✓ |
() |
— | ✓ | 分组,直接写括号 |
⚠重要:ERE没有反向引用,如果要
\1捕获,必须切回BRE。
POSIX方括号字符集(可移植优先用这个,不要写[a‑z])
必须套两层方括号 [[:xxx:]]
| 字符集 | 含义 |
|---|---|
[[:alnum:]] |
字母+数字 |
[[:alpha:]] |
大小写字母 |
[[:digit:]] |
数字0‑9 |
[[:lower:]] |
小写字母 |
[[:upper:]] |
大写字母 |
[[:blank:]] |
空格、tab |
[[:space:]] |
全部空白字符 |
[[:punct:]] |
标点符号 |
注意:
[:digit:]不能单独用!外面还要包一层[],写成[[:digit:]]。
简单正则示例
| 表达式 | 匹配说明 |
|---|---|
^tolstoy |
行开头是tolstoy |
tolstoy$ |
行结尾是tolstoy |
^tolstoy$ |
整行恰好等于tolstoy |
tol.*toy |
tol后面任意字符(0个及以上),后面接toy |
单词边界(GNU扩展,非POSIX标准,可移植脚本慎用)
| 写法 | 含义 |
|---|---|
\<word |
单词开头 |
word\> |
单词结尾 |
\<word\> |
完整独立单词 |
示例
|
|
注意:awk、POSIX标准正则没有
\< \>;属于GNU grep/sed扩展。
3.3 sed流编辑器
sed:读取输入流,按脚本指令处理,输出到标准输出;默认不修改原文件。
sed语法
| 选项 | 作用 |
|---|---|
‑n |
关闭自动打印;只打印p命令显式输出的行,p命令是 sed 编辑指令 |
‑e cmd |
一条编辑脚本,多条命令写多个‑e |
‑f file |
从文件读取sed脚本 |
区分:
-n -e -f:shell传给sed的启动参数,写在单引号外面- 单引号
' '内部:sed脚本语言,s///、p、d、地址匹配
sed替换命令 s/查找/替换/标记
- 定界符:默认
/;遇到路径大量斜杠,可以换别的符号如s:aaa:bbb:,不需要转义大量/。 - 替换标记:
g:global全局,一行内全部匹配替换;不加g只替换该行第一个匹配- 数字:
s/a/b/2只替换该行第2处匹配
- 替换文本特殊符号:
&:代表整个正则匹配到的全部内容\1 \2 ... \9:BRE捕获分组反向引用
示例
sed 地址(筛选哪些行执行命令)
格式:
地址 命令;没有地址,命令作用全部行
sed 的地址:就是筛选器:用来指定,哪些行要执行后面的 sed 命令
| 地址写法 | 说明 |
|---|---|
/regex/ |
匹配正则的行 |
n |
第n行;$最后一行 |
n,m |
从第n行到第m行(行号区间) |
/re1/,/re2/ |
从匹配re1的行开始,直到匹配re2的行结束 |
/re/! cmd |
不匹配re的行才执行cmd;!紧贴前面,不要空格 |
N q |
读到N行执行q退出;模拟head |
示例
空模式
s//xxx/:复用最近一次成功的正则匹配。
例如:sed ‘/Tom/ s/Tom/TOM/’ test.txt,/Tom/是地址筛选器,不属于 s 命令内部。s//TOM/是一条完整独立的 s 替换命令,只是 s 里面查找位置写空,复用上边地址匹配到的内容。sed ’s/Tom/TOM/’ test.txt,这里没有写地址,所以s/Tom/TOM/这条替换命令,对文件所有行全部执行。
sed重要规则
- 模式空间:sed读入每行放到模式空间做处理,处理完输出;
- 默认自动打印模式空间;
‑n关闭自动打印,只有p才输出; q命令:直接终止sed程序,不再读后续输入;- 正则是最长最左匹配:
T.*y会尽可能向后匹配到最后一个y。
⚠坑:
*修饰紧靠左边那一个字符,不是“任意字符”;.才代表任意单个字符。
b*:匹配0个或多个b,ac也能匹配(中间0个b)。因为b*允许0 个 b,ac里面没有 b,刚好符合条件。例如:echo "ac" | grep 'b*'这条命令会匹配成功,输出ac。
3.4 字段处理工具 cut / join / awk
记录:一行;字段:一行被分隔符切开的一列列数据。
cut:截取字符/截取字段
两种模式:
-c:按字符截取-f:按字段截取,‑d设置单个字符分隔符;默认分隔符tab。
| 参数 | 说明 |
|---|---|
‑s |
配合‑f:没有分隔符的行直接丢弃,不输出;不加‑s原样输出整行 |
范围写法(‑c、‑f通用)
| 写法 | 含义 |
|---|---|
n |
第n项 |
n,m |
第n、m项 |
n‑m |
n到m |
‑m |
开头到m |
n‑ |
n到行末尾 |
示例
硬限制:
‑d只能单个字符,多字符分隔不能用cut,改用awk。
join:按键合并两个文件(类似数据库内连接inner‑join)
硬性前提:两个输入文件,用于join的key必须提前排序!否则结果错乱,不会报错。
语法:
join [-1 N] [-2 N] [-t 分隔符] file1 file2
| 选项 | 说明 |
|---|---|
‑1 n |
file1使用第n字段作为连接键 |
‑2 n |
file2使用第n字段作为连接键 |
‑t char |
设置输入输出分隔符;默认任意空白(多个空格/tab视为一个分隔) |
‑o 1.1 -o 2.2 |
指定输出哪些字段,1.1代表file1第1字段 |
默认行为:只输出两边都有相同key的记录,单边独有的丢弃(内连接)。
emp.txt(文件 1:姓名:员工 ID: 岗位)
dept.txt(文件 2:员工 ID: 部门)
|
|
shell 把每一个 <(command) 替换成一个特殊文件名(不是磁盘普通文件,是内存管道文件,类似 /dev/fd/63、/dev/fd/62)。展开之后,join 实际收到的参数等价于:
|
|
/dev/fd/63→ 对应第一个 sort 的输出流/dev/fd/62→ 对应第二个 sort 的输出流
join 程序拿到两个文件名参数,join 内部:打开第一个 fd 读 sort‑emp 的结果,打开第二个 fd 读 sort‑dept 的结果。
<(...)产生的是一个文件名参数,传给命令,不是输入重定向。- 普通
< file是重定向,改变标准输入; <(...)生成文件名,当做命令的位置参数(join 需要两个输入文件,正好需要两个文件名)。
输出结果
awk基础(字段处理)
awk程序基础格式:pattern { action }
- pattern:匹配条件;省略pattern,全部行执行action
- action:处理动作;省略action等价于
{print}打印整行
常用内置变量
| 变量 | 含义 |
|---|---|
$0 |
整行完整记录 |
$1 $2 ... |
第1、第2字段…… |
NF |
当前行字段总数量;$NF代表最后一个字段 |
‑F: |
命令行选项,设置输入字段分隔符 |
FS |
输入字段分隔符;BEGIN块设置 |
OFS |
输出字段分隔符 |
print里面逗号不可丢,逗号会插入OFS输出分隔符;直接写字符串拼接不会加分隔。
BEGIN { ... }:读取任何输入行之前执行一次;
END { ... }:全部输入读取完毕之后执行一次。
本章速查卡片
| 命令 | 核心用途 | 示例 |
|---|---|---|
grep -E |
ERE扩展正则匹配 | grep -E 'cat|dog' file |
grep -F |
纯字面字符串匹配 | grep -F "hello world" file |
grep -v |
反向,输出不匹配行 | grep -v '^$' file |
sed 's/a/b/g' |
文本替换,g全局 | sed 's/old/new/g' file |
sed -n '/re/p' |
模拟grep,只打印匹配行 | sed -n '/root/p' /etc/passwd |
cut -d: -f1,5 |
按分隔符截取字段 | cut -d: -f1,5 /etc/passwd |
join -t: f1 f2 |
按键合并两个已排序文件 | join -t: user.txt info.txt |
awk -F: '{print $1}' |
awk字段处理 | awk -F: '{print $1}' /etc/passwd |
本章小结
- BRE/ERE区分:ERE
(){}+|直接写;BRE要加反斜杠\( \)\{ \};ERE不支持\1反向引用。 - 正则最长最左匹配:
.*会尽可能吃掉更多字符。 join必须按key排序,不排序输出错乱,不会报错。cut -d只支持单个字符分隔符,复杂分隔交给awk。- sed
-n是外部参数,不能写进单引号脚本内部。 $(( ))是shell算术;grep/sed/awk里面是正则,两套语法不要混淆。- 脚本尽量优先用
[[:digit:]]这类POSIX字符集,少用[a‑z],规避locale语言环境带来的匹配异常。
文章作者 会写代码的小郎中
上次更新 2011-07-16
许可协议 CC BY-NC-ND 4.0