文本处理(一)
本文主要讲述正则表达式,grep,awk,sed命令。
3个命令都是针对行进行处理的。
grep: 主要用来进行字符串在文件中的搜索。
示例: grep -l "hello" test_file
awk : 主要用来进行格式化的文件中的域分割及处理。
示例:awk -F : ' { print $1,$6} ' /etc/passwd
sed : 主要用来对文件中的字符串修改,比如替换字符串。
示例:在整行范围内把字符 22 替换为 kokotest,如果没有g标记,
则只有每行
第一个匹配的 22 被替换成 kokotest,-i选项指定对输入文件处理。
sed -i 's/22/kokotest/g' /root/soft/iptables
第7章 正则表达式
当从一个文件或命令输出中抽取或过滤文本时,可以使用正则表达式(RE:Regular Expression),
正则表达式是一些特殊或不很特殊的字符串模式的集合。
本章设计的基本元字符使用在grep和sed命令中,同时结合{ \ \ }(以字符出现情况进行匹配
的元字符)使用在awk语言中。
基本元字符集及其含义
^ 只只匹配行首
$ 只只匹配行尾
* 只一个单字符后紧跟*,匹配0个或多个此单字符
[ ] 只匹配[ ]内字符。可以是一个单字符,也可以是字符序列。可以使用-
表示[ ]内字符序列范围,如用[ 1-5 ]代替[ 1 2 3 4 5 ]
\ 只用来屏蔽一个元字符的特殊含义。因为有时在shell中一些元字符有
特殊含义。\可以使其失去应有意义
. 只匹配任意单字符
pattern \ { n \ } 只用来匹配前面pattern出现次数。n为次数
pattern \ { n,\ } m 只含义同上,但次数最少为n
pattern \ { n,m \ } 只含义同上,但pattern出现次数在n与m之间
^ 只只匹配行首
$ 只只匹配行尾
* 只一个单字符后紧跟*,匹配0个或多个此单字符
[ ] 只匹配[ ]内字符。可以是一个单字符,也可以是字符序列。可以使用-
表示[ ]内字符序列范围,如用[ 1-5 ]代替[ 1 2 3 4 5 ]
\ 只用来屏蔽一个元字符的特殊含义。因为有时在shell中一些元字符有
特殊含义。\可以使其失去应有意义
. 只匹配任意单字符
pattern \ { n \ } 只用来匹配前面pattern出现次数。n为次数
pattern \ { n,\ } m 只含义同上,但次数最少为n
pattern \ { n,m \ } 只含义同上,但pattern出现次数在n与m之间
7.1 使用句点匹配单字符
句点“.”可以匹配任意单字符。例如,如果要匹配一个字符串,以beg开头,中间夹一个
任意字符,那么可以表示为beg.n,“.”可以匹配字符串头,也可以是中间任意字符。
注意:此处是"."号。与文件搜索所使用的单字符?不同,这个"."是用在正则表达式中,
是在grep,sed,awk等命令的模式匹配的地方使用的。
在ls -l命令中,可以匹配一定权限:
. . . x . . x . . x
也就是属主用户,属主用户组,其他用户均具有执行权限。所以:
drwxrwxrw- - 不匹配
-rwxr-xr-x - 匹配
7.2 在行首以^匹配字符串或字符序列
注意,我们之前使用的ls -l | grep ^d示例中使用了^符号,文件搜索及查看
章节是没有直接使用这个符号的,这个符号只用在正则表达式匹配中。
可以将各种模式结合使用,例如:
^ . . . 4XC . . . .
将匹配所有第4-6个字符为4XC,长度为10个字符的字符串,所以:
1234XC9088 -匹配
9931XC3445 -不匹配
假定重新定义匹配模式,行首前4个字符为comp,后面紧跟两个任意字符,并以ing结尾,
一种方法为:
^ comp . .ing
一种方法为:
^ comp . .ing
7.3 在行尾以$匹配字符串或字符
假定要匹配以单词trouble结尾的所有行,操作为:
trouble$
trouble$
如果要匹配所有空行,执行以下操作:
^$
具体分析为匹配行首,又匹配行尾,中间没有任何模式,因此为空行。
7.4 使用*匹配字符串中的单字符或其重复序列
使用此特殊字符匹配任意字符或字符串的重复多次表达式。例如:
compu*t
将匹配字符u一次或多次:
compu*t
将匹配字符u一次或多次:
computer
computing
compuuute
也就是说u*,等同于0个或多个字符u。所以compt也是其匹配。
7.5 使用\屏蔽一个特殊字符的含义
有时需要查找一些字符或字符串,而它们包含了系统指定为特殊字符的一个字符。什么
是特殊字符?一般意义上讲,下列字符可以认为是特殊字符:
是特殊字符?一般意义上讲,下列字符可以认为是特殊字符:
$ . ' " * [ ] ^ | () \ + ?
假定要匹配包含字符“ .”的各行而“,”代表匹配任意单字符的特殊字符,因此需要屏蔽
其含义。操作如下:
\ .
上述模式不认为反斜杠后面的字符是特殊字符,而是一个普通字符,即句点。
假定要匹配包含^的各行,将反斜杠放在它前面就可以屏蔽其特殊含义。如下:
\^
如果要在正则表达式中匹配以*.pas结尾的所有文件,可做如下操作:
\*\.pas
即可屏蔽字符*的特定含义。
其含义。操作如下:
\ .
上述模式不认为反斜杠后面的字符是特殊字符,而是一个普通字符,即句点。
假定要匹配包含^的各行,将反斜杠放在它前面就可以屏蔽其特殊含义。如下:
\^
如果要在正则表达式中匹配以*.pas结尾的所有文件,可做如下操作:
\*\.pas
即可屏蔽字符*的特定含义。
7.6 使用[]匹配一个范围或集合
如果熟知一个字符串匹配操作,应经常使用[ ]模式。
假定要匹配任意一个数字,可以使用:
[0123456789]
然而,通过使用“-”符号可以简化操作:
[0-9]
或任意小写字母
[a-z]
要匹配任意字母,则使用:
[A-Za-z]
表明从A-Z、a-z的字母范围。
如要匹配任意字母或数字,模式如下:
[ A-Za-z0-9]
假定要匹配任意一个数字,可以使用:
[0123456789]
然而,通过使用“-”符号可以简化操作:
[0-9]
或任意小写字母
[a-z]
要匹配任意字母,则使用:
[A-Za-z]
表明从A-Z、a-z的字母范围。
如要匹配任意字母或数字,模式如下:
[ A-Za-z0-9]
注意,以上都是匹配某个范围内的一个字符。
示例:
当前目录下有很多文件(普通文件和目录文件),为了找到当前目录下以大写字母开头的目录文件,
我们可以使用find命令或者ls,grep命令的组合。由于find命令会去当前目录及其子目录搜索,
当子目录中的文件较多时,将会有很多结果输出,很多也是子目录的内容,不符合当前的要求。
所以,我们在这里使用ls,grep命令的组合。
通过 ls -l | grep ^d 获取当前目录下的目录文件列表,如下:
$ ls -l | grep ^d
drwxr-xr-x 2 user007 guest 4096 Dec 4 19:10 source
drwxr-xr-x 4 user007 guest 4096 Jan 9 21:03 obj
drwxr-xr-x 4 user007 guest 4096 Jan 5 11:08 Doc
注意,ls -l命令的输出是文件属性相关信息,不是文件,那么就不能对ls的结果使用find命令。
$ ls -l | grep ^d | grep [A-Z]
drwxr-xr-x 2 user007 guest 4096 Dec 4 19:10 source
drwxr-xr-x 4 user007 guest 4096 Jan 9 21:03 obj
drwxr-xr-x 4 user007 guest 4096 Jan 5 11:08 Doc
为什么没有得到预想的结果?因为grep处理的是一行字符串,而每行字符串的“月份”信息都是以
大写字母开始的,如上面的Jan,Dec。为了得到最终的结果Doc,我们必须分离出最后一列,于是:
$ ls -l | grep ^d | awk '{print $9}' | grep [A-Z]
drwxr-xr-x 4 user007 guest 4096 Jan 5 11:08 Doc
经常使用的正则表达式举例
^ 行首
$ 行尾
^[the] 以字母t,h,e之一开头的行
[Ss]igna[lL]\. 匹配单词signal,signaL,Signal,SignaL之一,但加一句点 (注意此处有转义符)
[mayMAY] 包含m,a,y大写或小写字母的行
^USER$ 只包含USER的行 (以USER开头,也以USER结尾,中间又无其他字符)
[tty]$ 以tty结尾的行
\. 带句点的行
^d..x..x..x 对用户、用户组及其他用户组成员有可执行权限的目录
^[^l] 排除关联目录的目录列表(列表开头不是以字母"l"开头)
[iI][nN] 大写或小写i或n
[^$] 空行
[^.*$] 匹配行中任意字符串
^......$ 包括6个字符的行
[a- zA-Z] 任意单字符
[a-z][a-z]* 至少一个小写字母
[^0-9\$] 非数字或美元标识
[^0-9A-Za-z] 非数字或字母
[123] 1到3中一个数字
第8章 Grep
相信grep是UNIX和LINUX中使用最广泛的命令之一。grep(全局正则表达式版本)允许
对文本文件进行模式查找。如果找到匹配模式, grep打印包含模式的所有行。grep支持
基本正则表达式,也支持其扩展集。grep有三种变形,即:
Grep:标准grep命令,本章大部分篇幅集中讨论此格式。
Egrep:扩展grep,支持基本及扩展的正则表达式,但不支持\q模式范围的应用,与之相
对应的一些更加规范的模式,这里也不予讨论。
Fgrep:快速grep。允许查找字符串而不是一个模式。不要误解单词fast,实际上它与grep
速度相当。
8.1 Grep
grep一般格式为:
grep [选项]基本正则表达式[文件]
这里基本正则表达式可为字符串。
grep [选项]基本正则表达式[文件]
这里基本正则表达式可为字符串。
8.1.1 双引号引用
在grep命令中输入字符串参数时,
最好将其用双引号括起来
。例如:“mystring”。这样做
有两个原因,
一是以防被误解为shell命令,二是可以用来查找多个单词组成的字符串,
例如:
“jet plane”,如果不用双引号将其括起来,那么单词plane将被误认为是一个文件,查询结果
将返回“文件不存在”的错误信息。
在调用变量时,也应该使用双引号,
诸如:grep“$MYVAR”文件名,如果不这样,将
没有返回结果。
在调用模式匹配时,应使用单引号。
8.1.2 grep常用选项
常用的grep选项有:
-c 只输出匹配行的计数。
-i 不区分大小写(只适用于单字符)。
-h 查询多文件时不显示文件名。
-l 查询多文件时只输出包含匹配字符的文件名。
-n 显示匹配行及行号。
-s 不显示不存在或无匹配文本的错误信息。
-v 显示不包含匹配文本的所有行。
在所有文件中查询单词“ sort it”
$ grep "sort it" *
$ grep -c "48"data.f
$ 4
grep返回数字4 ,意义是有4行包含字符串“48”。
现在显示包含“48”字符串的4行文本:
$ grep "48" data.f
48 Dec 3BC1997 LPSX 68.00 LVX2A 138
483 Sept 5AP1996 USP 65.00 LVX2C 189
484 Nov 7PL1996 CAD 49.00 PLV2C 234
483 May 5PA1998 USP 37.00 KVM9D 644
显示满足匹配模式的所有行行数:
$ grep -n "48" data.f
1:48 Dec 3BC1997 LPSX 68.00 LVX2A 138
2:483 Sept 5AP1996 USP 65.00 LVX2C 189
5:484 Nov 7PL1996 CAD 49.00 PLV2C 234
6:483 May 5PA1998 USP 37.00 KVM9D 644
行数在输出第一列,后跟包含4 8的每一匹配行。
显示所有不包含48的各行:
$ grep -v "48" data.f
47 Oct 3ZL1998 LPSX 43.00 KVM9D 512
219 Dec 2CC1999 CAD 23.00 PLV2C 68
216 Sept 3ZL1998 USP 86.00 KVM9E 234
当要匹配tab符号时,一种使用方式是\>。示例如下:
test文件的内容为:
abc help
abc1 help
所以,当我们需要“筛选”出abc所在的行时,可以按如下示例来:
$ grep "abc\>"
abc help
想对应的,如果是使用grep "abc“,结果如下:
$ grep "abc"
abc help
abc1 help
缺省情况下,grep是大小写敏感的,如要查询大小写不敏感字符串,必须使用- i选项。
如:abc help
ABC help
$ grep -i "abc" test
abc help
ABC help
8.2 grep和正则表达式
使用正则表达式使模式匹配加入一些规则,因此可以在抽取信息中加入更多选择。使用
正则表达式时最好用单引号括起来,这样可以防止grep中使用的专有模式与一些shell命令的
特殊方式相混淆。
8.2.1 模式范围
示例:(抽取包含483或484的行)
$ grep '48[34]' data.f
8.2.2 不匹配行首
如果要抽出记录,使其行首不是48,可以在方括号中使用^记号,表明查询在行首开始。
$ grep '^[^48]' data.f
8.2.3 设置大小写
使用- i开关可以屏蔽月份Sept的大小写敏感,也可以用[ ]模式。
8.2.4 匹配任意字符
匹配头两个是大写字母,并以C结尾的行。
$ grep '[A-Z] [A-Z]C$' test_file
8.2.5 模式出现几率
抽取包含数字4至少重复出现两次的所有行,方法如下:
$ grep '4\{2,\}' data.f
483 May 5PA1998 USP 37.00 KVM9D 644
如果要查询重复出现次数一定的所有行,语法如下,数字9重复出现两次:
$ grep '9\{2\}' data.f
有时要查询重复出现次数在一定范围内,比如数字或字母重复出现2到6次,下例匹配数
字8重复出现2到6次,并以3结尾:
$ grep '8\{2,6}3$' myfile
83 -- not match
88883 -- match
8884 -- not match
8888883 -- match
8.2.6 使用grep匹配“与”,“或”模式
grep 命令加-E 参数,这一扩展允许使用扩展模式匹配。例如,要抽取城市代码为219或
216,方法如下:
$ grep -E '219|216' data.f
219 Dec 2CC1999 CAD 23.00 PLV2C 68
216 Sept 3ZL1998 USP 86.00 KVM9E 234
8.2.7 匹配空行
结合使用^和$可查询空行。使用- n参数显示实际行数:
$ grep '^$' myfile
8.2.8 匹配特殊字符
查询有特殊含义的字符,诸如$ . ' " * [] ^ | \ + ? ,必须在特定字符前加\。假设要查询包含“.”
的所有行,脚本如下:
的所有行,脚本如下:
如要查询文件名conftroll.conf(这是一个配置文件),脚本如下:
$ grep 'conftroll\.conf' myfile
8.3 类名
grep允许使用国际字符模式匹配或匹配模式的类名形式。
表8-1 类名及其等价的正则表达式
类 等价的正则表达式 类 等价的正则表达式
[[:upper:]] [A-Z] [[:alnum:]] [0-9a-zA-Z]
[[:lower:]] [a-z] [[:space:]] 空格或tab键
[[:digit:]] [0-9] [[:alpha:]] [a-zA-Z]
现举例说明其使用方式。要抽取产品代码,该代码以5开头,后跟至少两个大写字母。使
用的脚本如下:
$ grep '5[[:uppper:]][[:upper:]]' data.f
483 Sept 5AP1996 USP 65.00 LVX2C 189
483 May 5PA1998 USP 37.00 KVM9D 644
之前说过,[字符]* 这个组合匹配的结果是包含此字符0个或多个的集合。
举例:testfile文件的内容如下:
looks
likes
ls
ls123
那么,我们输入以下命令后的结果分别如下:
$ grep 'l.*s' testfile
looks
likes
ls
ls123
是指包含l,s,且l,s之间可有0个,1个,或多个字符。
$ grep 'l.*k.' testfile
looks
likes
是指包含l,k,且l,k之间可有0个,1个,或多个字符。k之后至少要有一个字符。
$ grep 'ooo*' testfile
looks
*与其之前的o一起,那么搜索的匹配是包含2个或更多个连续的字符o的字符串。
如果想屏蔽终端上的错误提示,可以将标准输出和标准错误都重定向到/dev/null。
如: grep "louise" /etc/password > /dev/null 2>&1
8.4 egrep
egrep代表expression 或extended grep,适情况而定。egrep接受所有的正则表达式,egrep
的一个显著特性是可以以一个文件作为保存的字符串,然后将之传给egrep作为参数,为此使
用-f开关。如果创建一个名为grepstrings的文件,并输入484和47:
$ cat grepstrings
484
47
$ egrep -f grepstrings data.f
上述脚本匹配data.f中包含484或47的所有记录。当匹配大量模式时, - f开关很有用,而在
一个命令行中敲入这些模式显然极为繁琐。
一个命令行中敲入这些模式显然极为繁琐。
第9章 Awk
如果要格式化报文或从一个大的文本文件中抽取数据包,那么awk可以完成这些任务。它
在文本浏览和数据的熟练使用上性能优异。
为获得所需信息,文本必须格式化,即用域分隔符划分抽取域。
9.1 调用awk
awk [-F field-seperator] 'commands' input-file(s)
commands是真正的awk命令
如果设置了- F选项,则awk每次读一条记录或一行,并使用指定的分隔符分隔指定域,但
如果未设置- F选项,awk假定空格为域分隔符,并保持这个设置直到发现一新行。当新行出现
时,awk命令获悉已读完整条记录,然后在下一个记录启动读命令,这个读进程将持续到文件
尾或文件不再存在。
如果未设置- F选项,awk假定空格为域分隔符,并保持这个设置直到发现一新行。当新行出现
时,awk命令获悉已读完整条记录,然后在下一个记录启动读命令,这个读进程将持续到文件
尾或文件不再存在。
执行awk脚本:awk -f awk-script-file input-files
其中,-f选项指明在文件awk-script-file中的awk脚本。input-file(s)是要使用脚本处理的文件。
使用$1 , $3表示参照第1和第3域,$0表示所有域。举例如下:
为加入tab键,使用tab键速记引用符\t,后面将对速记引用加以详细讨论。也可
以为输出文本加入信息头。本例中加入name和belt及下划线。下划线使用\n,强迫启动新行,
并在\n下一行启动打印文本操作。打印信息头放置在BEGIN模式部分,因为打印信息头被界
定为一个动作,必须用大括号括起来。
以为输出文本加入信息头。本例中加入name和belt及下划线。下划线使用\n,强迫启动新行,
并在\n下一行启动打印文本操作。打印信息头放置在BEGIN模式部分,因为打印信息头被界
定为一个动作,必须用大括号括起来。
$ awk 'BEGIN { print "Name Belt\n-------------------------------------"}
{ print $1"\t"$4} END {"----------------------------------\nend-of-report\n"}' grade.txt
Name Belt
------------------------------
M.ZTansley Green
J.Lulu Yellow
------------------------------
end-of-report
当第一次使用awk时,可能被错误信息搅得不知所措,但通过长时间和不断的学习,可总
结出以下规则。在碰到a w k错误时,可相应查找:
? 确保整个awk命令用单引号括起来。
? 确保命令内所有引号成对出现。
? 确保用花括号括起动作语句,用圆括号括起条件语句。
? 可能忘记使用花括号,也许你认为没有必要,但awk不这样认为,将按之解释语法。
9.2 元字符
\ ^ $ . [] | () * + ?
其中, + 和 ? 只适用于awk。
+ 使用+ 匹配一个或多个字符。
?匹配模式出现频率。例如使用/XY?Z/匹配XYZ或YZ。
9.2.1 条件操作符
操 作符 描述 操作符 描述
< 小于 > = 大于等于
<= 小于等于 ~ 匹配正则表达式
== 等于 !~ 不匹配正则表达式
!= 不等于
~ 匹配正则表达式
!~ 不匹配正则表达式
(1)匹配
为使一域号匹配正则表达式,使用符号'~’后紧跟正则表达式,也可以用i f语句。a w k
中if后面的条件用()括起来。
中if后面的条件用()括起来。
观察文件grade.txt,如果只要打印brown腰带级别可知其所在域为field-4,这样可以写出
表达式{if($4~/brown/) print }意即如果field-4
包含
brown,打印它。如果条件满足,则打印匹
配记录行。可以编写下面脚本,因为这是一个动作,必须用花括号{}括起来。
$ awk '{ if($4~/Brown/) print $0}' grade.txt
J.Troll 07/99 4842 Brown-3 12 26 26
匹配记录找到时,如果不特别声明, awk缺省打印整条记录。
(2)精确匹配
~得到的结果是包含指定匹配串的行。但如果要精确匹配,需要使用== 。示例如下:
$ awk '$3=="48" {print $0}' grade.txt
P.Bunny 02/99 48 Yellow 12 35 28
(3)不匹配
在~之前加入!即可。下面的例子是找出
field-4
不
包含
brown的行。
$ awk '{ if($4 !~/Brown/) print $0}' grade.txt
其他比较操作(大于,大于等于 等操作符的使用不再赘述。
(4)设置大小写
为查询大小写信息,可使用[]符号。在测试正则表达式时提到可匹配[]内任意字符或单词,
因此若查询文件中级别为green的所有记录,不论其大小写,表达式应为'/[Gg]reen/'
因此若查询文件中级别为green的所有记录,不论其大小写,表达式应为'/[Gg]reen/'
$ awk '/[Gg]reen/' grade.txt
M.ZTansley Green
(5)匹配任意字符
使用句点.可匹配任意字符。表达式/^...a/意为行首前三个字符任意,第四个是a,尖角符号代表行首。
$ awk '$1 ~/^...a/' grade.txt
如果查询文本文件行首包含4 8的代码,可简单使用下面^符号:
$ awk '/^48/' input-file
(6)或关系匹配
为抽取级别为yellow或brown的记录,
使用竖线符|。意为匹配| 两边模式之一
。注意,使
用竖线符时,语句必须用圆括号括起来。
$ awk '$0 ~/(Green|green)/' grade.txt
(7)复合操作符
复合模式或复合操作符用于形成复杂的逻辑操作(逻辑判断)。注意跟上面的“或关系匹配”区分开来。
&& AND :语句两边必须同时匹配为真。
|| OR :语句两边同时或其中一边匹配为真。
! 非 : 求逆
(8)设置输入域到域变量名
在awk中,设置有意义的域名是一种好习惯,在进行模式匹配或关系操作时更容易理解。
一般的变量名设置方式为name = $ n,这里name为调用的域变量名, n为实际域号。例如设置学
生域名为name,级别域名为belt,操作为name=$1;belts = $4。注意分号的使用,它分隔awk命
令。下面例子中,重新赋值学生名域为name,级别域为belts。查询级别为Yellow的记录,并
最终打印名称和级别。
一般的变量名设置方式为name = $ n,这里name为调用的域变量名, n为实际域号。例如设置学
生域名为name,级别域名为belt,操作为name=$1;belts = $4。注意分号的使用,它分隔awk命
令。下面例子中,重新赋值学生名域为name,级别域为belts。查询级别为Yellow的记录,并
最终打印名称和级别。
$ awk '{name = $1; belts = $4; if(belts ~/Yellow/) print name "is blet"
blets }' grade.txt
P.Bunny is belt Yellow
(9)赋值比较操作
下面的例子查询所有比赛中得分在27点以下的学生。
用引号将数字引用起来是可选的,“27”、27产生同样的结果。
用引号将数字引用起来是可选的,“27”、27产生同样的结果。
$ awk '{if ($6 < 27) print $0}' grade.txt
J.Lulu 06/99 48317 green 9 24 26
下例中给数字赋以变量名BASELINE和在BEGIN部分给变量赋值,两者意义相同。
$ awk 'BEGIN {BASELINE="27"} if($6 < BASELINE) print $0}' grade.txt
J.Lulu 06/99 48317 green 9 24 26
(10)修改数值域取值或文本域
当在awk中修改任何域时,重要的一点是要记住实际输入文件是不可修改的,修改的只是
保存在缓存里的awk副本。
保存在缓存里的awk副本。
也就是说我们看到的awk的结果是缓存里面的副本,是被修改过的。但是如果我们使用cat
命令查看文件,会发出先文件还是未被修改。
$ awk '{ if($1 == "Build") $1 = "test"} { print $0} test_file
$ cat test_file
下面的例子实现计算当前目录下所有文件的长度(KB)。
首先用ls -l命令查看一下文件属性。注意第二个文件属性首字符为d,说明它是
一个目录,文件长度是第5列,文件名是第9列。如果系统不是这样排列文件名及其长度,应
适时加以改变。
$ ls -l | awk ' /^[^d]/ {print $9"\t"$5} {tot += $5} END {print "total :" tot "KB" }'
dev_pkg.fail 345
failedlogin 12416
messages 4260
sulog 12810
utmp 1856
wtmp 7104
total : 41351 KB
9.2.2 awk内置变量
awk 有许多内置变量用来设置环境信息。这些变量可以被改变。
ARGC (Argument Count) 命令行参数个数
ARGV (Argument Values) 命令行参数排列
ENVIRON (Environment) 支持队列中系统环境变量的使用
FILENAME (Filename) awk浏览的文件名
FNR (Number of Records in File) 浏览文件的记录数
FS (File Separator) 设置输入域分隔符,等价于命令行 - F选项
NF (Number of Field) 浏览记录的域个数
NR (Number of Records) 已读的记录数
OFS (Output Field Separator) 输出域分隔符
ORS (Output Record Separator) 输出记录分隔符
RS (Record Separator) 控制记录分隔符
ARGC支持命令行中传入awk脚本的参数个数。ARGV是ARGC的参数排列数组,其中每
一元素表示为ARGV[n],n为期望访问的命令行参数。
一元素表示为ARGV[n],n为期望访问的命令行参数。
ENVIRON 支持系统设置的环境变量,要访问单独变量,使用实际变量名,例如
ENVIRON[“EDITOR”] =“Vi”。
FILENAME支持awk脚本实际操作的输入文件。因为awk可以同时处理许多文件,因此如
果访问了这个变量,将告之系统目前正在浏览的实际文件。
FNR为awk目前操作的文件中的记录数。其变量值小于等于NR。如果脚本正在访问许多文件,
每一新输入文件都将重新设置此变量。
FS用来在awk中设置域分隔符,与命令行中-F选项功能相同。缺省情况下为空格。如果用
逗号来作域分隔符,设置FS = ","。
NR为已读的记录数。所以在浏览开始,其值为0,浏览结束后,其值为记录个数。
OFS允许指定输出域分隔符,缺省为空格。如果想设置为#,写入OFS=" # "。
ORS为输出记录分隔符,缺省为新行(\n)。
RS是记录分隔符,缺省为新行(\n)。
示例:
$ awk 'BEGIN{print NR,FNR} END{print NR,FNR}' test_file1 test_file2
$ awk -F : '{OFS="\t"} {print $1,$7}' /etc/passwd
9.2.3 awk内置的字符串函数
gsub(r,s) 在整个$0中用s替代r
gsub(r,s,t) 在整个t中用s替代r
index(s,t) 返回s中字符串t的第一位置
length(s) 返回s长度
match(s,r) 测试s是否包含匹配r的字符串
split(s,a,fs) 在fs上将s分成序列a
sprint(fmt,exp) 返回经fmt格式化后的exp
sub(r,s) 用$0中最左边最长的子串代替s
substr(s,p) 返回字符串s中从p开始的后缀部分
substr(s,p,n) 返回字符串s中从p开始长度为n的后缀部分
gsub函数有点类似于sed查找和替换。它允许替换一个字符串或字符为另一个字符串或字
符,并以正则表达式的形式执行。第一个函数作用于记录$0,第二个gsub函数允许指定目标,
然而,如果未指定目标,缺省为$0。
index(s,t)函数返回目标字符串s中查询字符串t的首位置。
length函数返回字符串s字符
长度。
match函数测试字符串s是否包含一个正则表达式r定义的匹配。
split使用域分隔符fs将
字符串s划分为指定序列a。
sprint函数类似于printf函数(以后涉及),返回基本输出格式fmt的结果字符串exp。
sub(r,s)函数将用s替代$0中最左边最长的子串,该子串被(r)匹配。
sub(s,p)返回字符串s在位置p后的后缀。substr(s,p,n)同上,并指定子串长度为n。
(1)gsub
要在整个记录中替换一个字符串为另一个,使用正则表达式格式, /目标模式/,替换模式。
示例如下:
$ awk 'gsub(/build/,"test") {print $0}' test_file
autotest
test
test_linux_prog
test_linux_ko
gsub也是修改的缓存中的副本,原文件内容并未被修改。所以原文件对应的几行还是:
autobuild
build
build_linux_prog
build_linux_ko
(2)index
查询字符串s中t出现的第一位置。必须用双引号将字符串括起来。例如返回目标字符串
Bunny中ny出现的第一位置,即字符个数。
$ awk 'BEGIN { print index("Bunny","ny")}' grade.txt
4
(3)length
返回所需字符串长度。示例如下:
$ awk 'BEGIN { print length("Bunny")}'
5
(4)match
match测试目标字符串是否包含查找字符的一部分。可以对查找部分使用正则表达式,返
回值为第一次出现的字符位置。否则,返回0。
$ awk 'BEGIN { print match("ABCD",/d/)}'
0
$ awk 'BEGIN { print match("ABCD",/C/)}'
3
$ awk 'BEGIN { print match("ABCD","C")}'
3
(5)split
与在awk命令中设置FS或指定-F选项。返回拆分后的数组元素个数。
$ awk 'BEGIN { print split("ab-cd-ef-g",Myarray,"-")}
{ print Myarray[1],Myarray[2],Myarray[3],Myarray[4]}' test_file
4
ab cd ef g
上例中的test_file输入文件必须指定(从语法上),但实际上不起作用。
(6)sub
使用sub发现并替换模式的第一次出现位置。
$ awk 'BEGIN{STR = "ab-cd-ef-g"} {sub("-","#",STR)} END{print STR}' test_file
ab#cd#ef#g
或者
$ awk 'BEGIN{STR = "ab-cd-ef-g"} {sub(/-/,"#",STR)} END{print STR}' test_file
ab#cd#ef#g
但是第二个替换串不能是模式。示例如下:
$ awk 'BEGIN{STR = "ab-cd-ef-g"} {sub(/-/,/#/,STR)} END{print STR}' test_file
ab0cd0ef0g
(7)substr
substr是一个很有用的函数。它按照起始位置及长度返回字符串的一部分。例子如下:
$ awk '$1=="L.Tansley" {print substr($1,1,5)}' grade.txt
L.Tan
上例中,指定从第一个域第1个字符开始,长度为5的字串。
如果给定长度值远大于字符串长度, awk将从起始位置返回所有字符,要抽取L.Tansley
的姓,只需从第3个字符开始返回长度为7。可以输入长度99,awk返回结果相同。
substr的另一种形式是返回字符串后缀或指定位置后面字符。这里需要给出指定字符串及
其返回字串的起始位置。
$ awk '{print substr($1,3)}' grade.txt
(7)从shell中向awk传入字符串
可以通过管道的方式,如:
$ echo "Stand-by" | awk ' { print length($0)}'
8
9.2.4 字符串屏蔽序列
使用字符串或正则表达式时,有时需要在输出中加入一新行或查询一元字符。
打印一新行时,(新行为字符\n),给出其屏蔽序列,以不失其特殊含义,用法为在字符
串前加入反斜线。例如使用\n强迫打印一新行。
如果使用正则表达式,查询花括号({}),在字符前加反斜线,如/\{/,将在awk中失掉其
特殊含义。
|
|
awk 中使用的屏蔽序列
|
|
|
\b
|
退格键
|
|
\t
|
tab键
|
\f
|
走纸换页
|
|
\ddd
|
八进制值
|
\n
|
换行
|
|
\c
|
任意其他特殊字符
|
\\
|
反斜线符号
|
|
\r
|
回车键
|
9.2.5 awk里的输出函数printf
使用方法类似于C语言里面的格式化输出函数Printf。举例如下:
打印所有的学生名字和序列号,要求名字左对齐, 15个字符长度,后跟序列号。注意\ n
换行符放在最后一个指示符后面。输出将自动分成两列。
$ awk 'BEGIN { print "Name\t\tS.Number"} { printf "%-15s %s\n",$1,$3}' grade.txt
M.Tansley 48311
J.Lulu 48317
P.Bunny 48
J.Troll 4842
L.Tansley 4712
9.2.6 向awk命令传递变量值
在awk执行前将值传入awk变量,需要将变量放在命令行中,格式如下:
awk 命令变量=变量值
下面的例子在命令行中设置变量AGE等于10,然后传入awk中,查询年龄在10岁以下的
所有学生。
$ awk '{ if($5 < AGE) print $1,$5}' AGE=10 grade.txt
M.Tansley 8
J.Lulu 9
awk也允许传入环境变量。下面的例子使用环境变量LOGNAME支持当前用户名。可从
who命令管道输出到awk中获得相应信息。
$ who| awk ' if($1 == user) print $1 " is connected to "
$2}' user = $LOGNAME
如果root为当前登录用户,输出如下:
root is connected to ttyp1
9.2.7 在awk中使用FS变量
使用awk脚本时,记住设置FS变量是在BEGIN部分。如果不这样做,awk将会发生混淆,
不知道域分隔符是什么。以一个awk脚本为例。
#!/bin/awk -f
# This script is to get $1,$5 in file /etc/passwd
BEGIN { FS = ":"}
{ printf "%-15s%-15s\n",$1,$5}
假设此文件名为test_passwd.awk,那么调用它的命令为:
$ test_passwd.awk /etc/passwd
root root
bin bin
lp lp
mysql MySQL Server
9.2.8 向awk脚本传值
向awk脚本传值与向awk一行命令传值方式大体相同,格式为:
awk_script_file var=value input_file
不要忘了增加脚本的可执行权限,然后将变量和赋值放在命令行脚本名字后、输入文件
前执行。
9.2.9 awk数组
前面讲述split函数时,提到怎样使用它将元素划分进一个数组。
数组使用前,不必定义,也不必指定数组元素个数。经常使用循环来访问数组。下面是
一种循环类型的基本结构:
For (element in array ) print array[element]
对于记录“123#456#678”,先使用split函数划分它,再使用循环打印各数组元素。操作
脚本如下:
#!/bin/awk -f
#Filename : arraytest.awk
# prints out an array
BEGIN {
record = "123#456#78";
split(record,myarray,"#")
}
END {
for (i in myarray)
print myarray[i]
}
下面来看一个数组使用的例子:
awk脚本如下:
#!/bin/awk -f
# This AWK Script is used to categorized users
# by the terminal name they use to login
BEGIN{
term["(sdh01)"] = 0
term["(sdh03)"] = 0
term["(sdh08)"] = 0
}
{
for( t_name in term)
{
if($2 == t_name)
term[t_name]++
}
}
END {
for ( t_name in term)
{
print term[t_name] " users logged in terminal " t_name
}
}
$ array_test.awk who_out
2 users logged in terminal (sdh01)
0 users logged in terminal (sdh03)
1 users logged in terminal (sdh08)
其中,who_out文件,是我们通过who和awk命令截取的$1,$6
$ who | awk '{ print $1,$6 }' > who_out
值得一提的是此处数组的用法。此处的“下标”不是整数,而是字符串哦。这个
是与C语言不同之处。那么,根据awk这边数组的语法:
一种循环类型的基本结构:
For (element in array ) print array[element]
For循环中的那个element是”下标“,在例子中可以是(sdh01),(sdh03),(sdh08)。
那么,term[t_name]就是引用对应的数组元素。
第10章 Sed
本节将”全方位“介绍Sed,所涉及的内容比较细,繁杂。实际使用过程中,根据个人所需,
重点掌握少数几点即可。
同awk类似,sed操作的是文件在缓存中的内容,不直接改变文件本身。也可通过命令或者脚本
方式调用。
因为sed是一个非交互性编辑器,必须通过行号或正则表达式指定要改变的文本行。和grep与awk
一样,sed是一种重要的文本过滤工具,或者使用一行命令或者使用管道与grep与awk相结合。
10.1 sed怎样读取数据
sed从文件的一个文本行或从标准输入的几种格式中读取数据,将之拷贝到一个编辑缓冲
区,然后读命令行或脚本的第一条命令,并使用这些命令查找模式或定位行号编辑它。重复
此过程直到命令结束。
使用sed命令行格式为:
sed [选项] sed命令 输入文件。
sed [选项] sed命令 输入文件。
记住在命令行使用sed命令时,实际命令要加单引号。sed也允许加双引号。
使用sed脚本文件,格式为:
sed [选项] -f sed脚本文件 输入文件
要使用第一行具有sed命令解释器的sed脚本文件,其格式为:
sed脚本文件 [选项] 输入文件
sed选项如下:
n 不打印;sed不写编辑行到标准输出,缺省为打印所有行(编辑和未编辑)。p命令可以
用来打印编辑行(就是要处理的文本内容)。
c 下一命令是编辑命令。使用多项编辑时加入此选项。如果只用到一条sed命令,
此选项无用,但指定它也没有关系。
f 如果正在调用sed脚本文件,使用此选项。此选项通知sed一个脚本文件支持所有的sed
命令,例如:sed -f myscript.sed input_file,这里myscript.sed即为支持sed命令的文件。
i 直接对文件进行编辑
10.2.2 使用sed在文件中查询文本的方式
sed浏览输入文件时,缺省从第一行开始,有两种方式定位文本:
1) 使用行号,可以是一个简单数字,或是一个行号范围。
2) 使用正则表达式,怎样构建这些模式请参见第7章。
|
使用sed 在文件中
定位文本的方式
|
|
X
|
|
X 为一行号,如 1
|
x,y
|
|
表示行号范围从x到y,如 2,5表示从第2行到第5行
|
/pattern/
|
|
查询包含模式的行。例如 /disk/ 或/[a-z]/
|
/pattern/pattern/
|
|
查询包含两个模式的行。例如 /disk/disks/
|
pattern/,x
|
|
在给定行号上查询包含模式的行。如/ribbon/ ,3
|
x,/pattern/
|
|
通过行号和模式查询匹配行。 3./vdu/
|
x,y!
|
|
查询不包含指定行号x和y的行。1,2!
|
10.2.3 基本 sed 编辑命令
p | 打印匹配行 |
= | 显示文件行号 |
a\ | 在定位行号后附加新文本信息 |
i\ | 在定位行号后插入新文本信息 |
d | 删除定位行 |
c\ | 用新文本替换定位文本 |
s | 使用替换模式替换相应模式 |
r | 从另一个文件中读文本 |
w | 写文本到一个文件 |
q | 第一个模式匹配完成后推出或立即推出 |
l | 显示与八进制ASCII代码等价的控制字符 |
{} | 在定位行执行的命令组 |
n | 从另一个文件中读文本下一行,并附加在下一行 |
g | 将模式2粘贴到/pattern n/ |
y | 传送字符 |
n | 延续到下一输入行;允许跨行的模式匹配语句 |
10.3 基本sed编程举例
10.3.1 使用p(rint)显示行
print命令格式为[address[,address]p。显示文本行必须提供sed命令行号。
其中,[表示可选,示例如下:
$ sed '2p' quote.txt
原意只打印第二行,但是却打印了文件中所有行。
为此需使用-n选项,显示打印定位(匹配)行。
$ sed -n '2p' quote.txt
但是,如果你不指明行号,那么输出的内容将是文件内容的”双份“。
$ sed 'p' quote.txt
输出整个文件,除了sed -n 'p' quote.txt和sed '2p' quote.txt
这样的外,还可以使用最后一行符号"$"。
$ sed -n '1,$p' quote.txt
10.3.2 打印范围
可以指定行的范围,现打印1到3行,用逗号分隔行号。
$ sed -n '1,3p' quote.txt
或者(将p放到'外面),示例如下:
$ sed -n '1,3'p quote.txt
10.3.3 打印模式
假定要匹配单词Neave,并打印此行,方法如下。使用模式/pattern/格式,这里为/Neave/。
$ sed -n '/Neave/p' quote.txt
10.3.4 使用模式和行号进行查询
使用模式与行号的混合方式,格式为line_number,/pattern/。使用4,/the/,
意即只在第四行查询模式the,命令如下:
$ sed -n '4,/The/'p quote.txt
10.3.5 匹配元字符
匹配元字符$前,必须使用反斜线\屏蔽其特殊含义。模式为/\$/ p。
10.3.6 匹配任意字符
通过模式匹配,查询以ing结尾的任意单词。
$ sed -n '/.*ing/'p quote.txt
10.3.7 打印行号
要打印行号,使用等号=编辑选项。打印模式匹配的行号,使用格式/pattern/=。示例:
$ sed -n '/user1/=' login_file
但是如果要得到grep -n ”user1" login_file这样既打印 行号,就打印行内容的结果,那么
如果只打印行号及匹配行,必须使用两个sed命令,并使用e选项。第一个命令打印模式匹配行,
第二个使用=选项打印行号,格式为sed -n -e /pattern/p -e/pattern/=。
$ sed -n -e '/user1/p' -e '/user1/=' login_file
10.3.8 附加,插入,修改文本
使用sed的a\,i\,c\命令可以实现文本的附加,插入和修改。
附加是在给定的模式或行号后面添加,插入是在给定的模式或行号之前添加,而修改是用新文本替代。
注意处理的都是行(行号指定或模式匹配的行)。使用这些命令操作时,请使用sed脚本,示例如下:
#!/bin/sed -f
/build/ a\
Hello World. I am Android.
当然,我们也可以使用sed '/build/ a\ Hello World. I am Android' test_fle
10.3.9 删除文本
使用d命令,用法与p类似。[address[,address]d。示例:
$ sed '1,3d' quote.txt
删除第1至第三行。
10.3.10 替换文本
替换命令用替换模式替换指定模式,格式为:
[address[,address]] s/ pattern-to-find /replacement-pattern/[g p w n]
s选项通知sed这是一个替换操作,并查询pattern-to-find,成功后用replacement-pattern替换它。
替换选项如下:
g 缺省情况下只替换第一次出现模式,使用g选项替换全局所有出现模式。
p 缺省sed将所有被替换行写入标准输出,加p选项将使- n选项无效。- n选项不打印输出结果。
w 文件名使用此选项将输出定向到一个文件。
替换文本也是对缓冲区的副本进行操作,不会改变原文件的内容。示例:
$ sed 's/night/NIGHT/g' quote.txt
如果没有g,将只会替换匹配到的行中的第一次出现,而不会将行内的其他匹配替换。
要从$90中删除$ 符号(记住这是一个特殊符号,必须用\ 屏蔽其特殊含义),在
replacement-
pattern部分不写任何东西,保留空白,但仍需要用斜线括起来。在sed中也可以这样删除一个字符串。
$ sed 's/\$//' quote.txt
将替换结果写入一个文件用w选项,下面的例子将splendid替换为SPLENDID的替换结果
写入文件sed.out。注意要将文件名括在sed的单引号里。
$ sed 's/splendid/SPLENDID/g w sed.out' quote.txt
10.4 使用替换修改字符串
如果要附加或修改一个字符串,可以使用(&)命令,&命令保存发现模式以便重新调用
它,然后把它放在替换字符串里面。
它,然后把它放在替换字符串里面。
$ sed -n s/nurse/"Hello" &/p' quote.txt
The local "Hello" nurse .
原文是The local nurse。也就是说&其实保存的就是nurse,我们需要做的是将nurse替换
为"Hello" nurse。
10.5 从文件中读文本
处理文件时, sed允许从另一个文件中读文本,并将其文本附加在当前文件。此命令放在
模式匹配
行后,格式为:
address r filename
这里r通知sed将从另一个文件源中读文本。filename是其文件名。注意所读的文件名需要
用单引号括起来。
$ sed '/company/r sedtest.txt' quote.txt
10.6 匹配后退出
有时需要在模式匹配首次出现后退出sed,以便执行其他处理脚本。退出命令格式为:
address q
下面的例子假定查询模式/.a.*/,意为任意字符后跟字符a,再跟任意字符0次或任意多次。
查询首次出现模式,然后退出。需要将q放在sed语句末尾。
$ sed '/.a.*/q' quote.txt
10.7 使用系统sed
前面已经讲述了sed的基本功能,但是在脚本或命令行中使用sed真正要做的是修改或删除
文件或字符串中文本。
文件或字符串中文本。
例1:处理控制字符
12332##DISO##45.12$
00332##LPSO##23.11$
01299##USPD##34.46$
以上为dos.txt文件内容,$表示换行符。
(1)用一个空格替换所有的(##)符号。
(2)删除起始域中最前面的0(00)。
(3)删除行尾控制字符($)。
任务1。删除所有的#字符很容易,可以使用全局替换命令。这里用一个空格替换一个或
更多的#符号。
更多的#符号。
$ sed 's/##*//g' dos.txt
12332 DISO 45.12$
00332 LPSO 23.11$
01299 USPD 34.46$
任务2。删除所有行首的0。使用^符号表示模式从行首开始,^0*表示行首任意个0。
模式s/^0*//g设置替换部分为空,即为删除模式,正是要求所在。
$ sed 's/^00*//g' dos.txt
12332##DISO##45.12$
00332##LPSO##23.11$
01299##USPD##34.46$
任务3。删除行尾控制字符($)。
$ sed 's/\$//g' dos.txt
分步测试预想功能对理解整个过程很有帮助。用sed在移到下一步前测试本步功能及结果
很重要。将所有命令结合在一起,使用管道将cat命令结果传入一系列sed命令,
$ cat dos.txt | sed 's/##*//g' |
sed 's/^00*//g'
|
sed 's/\$//g'
12332 DISO 45.12
332 LPSO 23.11
1299 USPD 34.46
也可通过sed脚本来完成。脚本示例:
#!/bin/sed -f
#name : dos.sed
# to calll : dos.sed dos.txt
#get rid of the hash marks
s/##*//g
# get rid of the leading zeros
s/^00*//g
#get rid of the carriage return
#here we use $ to stand for CR
s/\$//g
例2:处理控制字符
当从数据库中执行语句输出时,一旦有了输出结果,脚本即可做进一步处理。通常先做
一些整理,下面是一个sql查询结果。
一些整理,下面是一个sql查询结果。
Database Size(MB) Date Created
-----------------------------------
GOSOUTH 2244 12/11/97
TRISUD 5632 8/9/99
( 2 rows affected )
为了使用上述输出信息做进一步自动处理,需要知道所存数据库名称,为此需执行以下
操作:
1) 使用s/--* //g或者s/-*//g删除横线------。
其中,--*表示至少有一个-,及将一个或多个-替换为空字符。而-*表示0个或多个-。
因为空字符可用空字符替换(无变化),所以上面两种方式都是可以的。
2) 使用/^$/d删除空行。
3) 使用$d删除最后一行
4) 使用1d删除第一行。
5) 使用awk {print $1}打印第一列。
命令如下,这里使用了cat,并管道传送结果到sed命令。
$ cat sql.txt | sed 's/--*//g' | sed '/^$/d' | sed '$d'
| sed '1d' | awk ' { print $1 }'
例3:去除行首数字
去除行首第一个数字:$ sed 's/^[0-9]//g' test.txt
去除行首所有数字: $ sed 's/^[0-9][0-9]*//g' test.txt
去除行内所有数字: $ sed 's/[0-9][0-9]*//g' test.txt
例4:附加文本
帐号文件account.txt的内容是一对帐号,我们需要找出符合要求的(至少有一个数字出现),
并在帐户名后面添加字符串'passed'。
user123
user001
userqq
于是经过处理后,得到的结果为:
$ sed 's/[0-9][0-9]*/& Passed/g' account.txt
10.8 常用命令集
在下表中,[]表示空格, [ ]表示tab键
's/\.$//g' 删除以句点结尾行
'-e /abcd/d' 删除包含abcd的行
's/[][]*/[]/g' 删除一个以上空格,用一个空格代替
's/^[][]*//g' 删除行首空格
's/\.[][]*/[]/g' 删除句点后跟两个或更多空格,代之以一个空格
'/^$/d' 删除空行
's/^.//g' 删除第一个字符
's/COL\(...\)//g' 删除紧跟COL的后三个字母
's/^\///g' 从路径中删除第一个/
's/[]/[ ]/g' 删除所有空格并用tab键替代
's/^[ ]//g' 删除行首所有tab键
's/[ ]*//g' 删除所有tab键
'[a-x]*' 将匹配零或多个全部为 'a'、'b'、'c'...'v'、'w'、'x' 的字符。另外,
可以使用 '[:space:]' 字符类来匹配空格。以下是可用字符类的相当完整的列表:
字符类 描述
[:alnum:] 字母数字 [a-z A-Z 0-9]
[:alpha:] 字母 [a-z A-Z]
[:blank:] 空格或制表键
[:cntrl:] 任何控制字符
[:digit:] 数字 [0-9]
[:graph:] 任何可视字符(无空格)
[:lower:] 小写 [a-z]
[:print:] 非控制字符
[:punct:] 标点字符
[:space:] 空格
[:upper:] 大写 [A-Z]
[:xdigit:] 十六进制数字 [0-9 a-f A-F]
示例: sed -n -e '/main[[:space:]]*(/,/^}/p' sourcefile.c | more
一个地址的多个命令
要对一个地址执行多个命令,可在文件中输入 sed 命令,然后使用 '{ }' 字符将这些命令分组。
1,20{ s/[Ll]inux/GNU//Linux/g s/samba/Samba/g s/posix/POSIX/g }
把三个替换命令应用到第 1 行到第 20 行(包括这两行)
要对一个地址执行多个命令,可在文件中输入 sed 命令,然后使用 '{ }' 字符将这些命令分组。
1,20{ s/[Ll]inux/GNU//Linux/g s/samba/Samba/g s/posix/POSIX/g }
把三个替换命令应用到第 1 行到第 20 行(包括这两行)