- 高性能Linux服务器运维实战:shell编程、监控告警、性能优化与实战案例
- 高俊峰编著
- 4228字
- 2021-04-04 09:03:09
2.1 正则表达式与变量
2.1.1 正则表达式的组成与应用1.什么是正则表达式
正则表达式(Regular Expression,RE)就是由一系列特殊字符组成的字符串。其中每个特殊字符都被称为元字符,这些元字符并不表示它们字面上的含义,而是被解释为一些特定的含义。
正则表达式是由普通字符和元字符共同组成的集合,这个集合用来匹配(或指定)模式。正则表达式的主要功能是文本查询和字符串操作,正则表达式可以匹配文本的一个字符或字符集合。
例如,a、b、1、2等字符属于普通字符,普通字符可以按照字面意思理解,如a只能理解为英文的小写字母a,没有其他隐藏含义。而*、^、[ ]等元字符,shell赋予了它们超越字面意思的意义,如*符号的字面意义只是一个符号,而实际上却表示了重复前面的字符0次或多次的隐藏含义。
2.正则表达式的组成
一个正则表达式包含下列一项或多项。
➢ 一个字符集:这里所指的字符集只包含普通字符,这些字符只表示它们的字面含义。正则表达式的最简单形式就是只包含字符集,而不包含元字符。
➢ 锚:锚指定了正则表达式所要匹配的文本在文本行中所处的位置,如^和$就是锚。
➢ 修饰符:它们扩大或缩小(修改)了正则表达式匹配文本的范围。修饰符包含星号、括号和反斜杠。
正则表达式中常用的一些符号以及对应的意义见表2-1。
表2-1 正则表达式中符号含义
![](https://epubservercos.yuewen.com/680E1C/17977546608668706/epubprivate/OEBPS/Images/50_01.jpg?sign=1739487384-nGYbJBNoWradPiFNBCQNd5WaI5zbonV5-0-fceab1adc168d246ff48d26cb56991e6)
其中:
➢ *用于匹配前面一个普通字符的0次或多次重复,例如,hel*o,*符号前面的普通字符是l,*字符就表示匹配l字符0次或多次,如字符串helo、hello、hellllllo都可以由hel*o来表示。
➢ .用于匹配任意一个字符,例如,…73. 表示前面3个字符为任意字符,第4和第5个字符是7和3,最后一个字符为任意字符,如xcb738、4J973U都能匹配上述字符串。
➢ ^用于匹配行首,表示行首的字符是^字符后面的那个字符,例如:^cloud表示匹配以cloud开头的行
➢ $匹配行尾,$放在匹配字符之后,例如,micky$表示匹配以micky结尾的所有行,^$表示匹配空白行。
➢ []匹配字符集合,在正则表达式中,将匹配中括号字符集中的某一个字符,例如:
✧ [xyz]将会匹配字符x、y、或z。
✧ [c-n]匹配字符c~n之间的任意一个字符。
✧ [B-Pk-y]匹配从B~P,或者从k~y之间的任意一个字符。
✧ [a-z0-9]匹配任意小写字母或数字。
✧ [^b-d]将会匹配范围在b~d之外的任意一个字符。这就是使用^对字符集取反的一个实例。
✧ 将多个中括号字符集组合使用,能够匹配一般的单词或数字,例如,[Yy][Ee][Ss]能够匹配yes、Yes、YES、yEs等,[0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9][0-9][0-9]可以匹配社保码。
下面来看第1个例子,操作如下:
![](https://epubservercos.yuewen.com/680E1C/17977546608668706/epubprivate/OEBPS/Images/50_02.jpg?sign=1739487384-HHQ5ObTaVKdQWg8RkQiZLNHL5SMXTllQ-0-82a6b395f172c9ffc7cb309dbe1a0f07)
![](https://epubservercos.yuewen.com/680E1C/17977546608668706/epubprivate/OEBPS/Images/51_01.jpg?sign=1739487384-4MTVY5iDmhR6kB7VkzRSuTHkCNtR37Kb-0-877f350dedd5ac4418704d18dcb50951)
从输出可以看出,精确匹配了exp2.txt文件中所有的非字符内容。
➢ \用来转义某个特殊含义的字符,这意味着,这个特殊字符将会被解释为字面含义。例如:
➢ \$将会被解释成字符$,而不是RE中匹配行尾的特殊字符。相似的,\\将会被解释为字符\。
➢ 转义的尖括号\<...\>用于匹配单词边界,尖括号必须被转义才含有特殊的含义,否则它就表示尖括号的字面含义。
➢ \<the\>完整匹配单词the,不会匹配them、there、other等。
➢ \{\}系列符号表示前一个字符的重复次数。
✧ \{n\}匹配前面字符出现n次,如JO\{3\}B匹配JOOOB。
✧ \{n,\}匹配前面字符至少出现n次,如JO\{3,\}B匹配JOOOB、JOOOOB、JOOOOOB等字符串。
✧ \{n,m\}匹配前面字符出现n次与m次之间,如JO\{3,6\}B匹配JOOOB、JOOOOOOB等字符串。
例如,[a-z]\{5\}匹配5个小写英文字母,如hello、house等。
继续看第2个例子,操作如下:
![](https://epubservercos.yuewen.com/680E1C/17977546608668706/epubprivate/OEBPS/Images/51_02.jpg?sign=1739487384-qKh0s7ykcseTuau7B9dL2Pllp1umcN5P-0-a09bd3b49ed4ed5ff2546ed7e0b562d3)
![](https://epubservercos.yuewen.com/680E1C/17977546608668706/epubprivate/OEBPS/Images/52_01.jpg?sign=1739487384-PSkvQFcjqoQidCUAxFYJP5coAzRQN2hR-0-483a01f7975c8dd6743a78cdb6ec6aef)
可以看出,grep数字中精确匹配了iivey这个单词。
2.1.2 shell中的变量与应用
1.变量的定义与分类
变量用于保存有用信息,如路径名、文件名、数字等。Linux用户使用变量定制其工作环境,使系统获知用户相关的配置。变量本质上是存储数据的一个或多个计算机内存地址。
shell中的变量可分为如下几种。
➢ 用户自定义变量,如,myname,这类变量是由用户自己定义、修改和使用。
➢ shell环境变量PATH,这类变量是由系统维护,用于设置用户的shell工作环境,只有少数的变量用户可以修改其值。
➢ 位置参数变量(Positional Parameters),这类变量通过命令行给程序传递执行参数,可用shift命令实现位置参数的迁移。
➢ 内部参数变量(Special Parameters),这类变量是Bash预定义的特殊变量,用户不能修改其值。
2.变量的赋值
变量是某个值的名称,引用变量值称为变量替换,$符号是变量替换符号,如variable是变量名,那么$variable就表示变量的值。
变量赋值有两种格式:
![](https://epubservercos.yuewen.com/680E1C/17977546608668706/epubprivate/OEBPS/Images/52_02.jpg?sign=1739487384-gzZSWPrBdvxNZXX57gE8xB6it9pbZGAI-0-f9c092dc14d19b6825031c113284faac)
切记等号的两边不可以有空格;如果值(value)中包含空格,则必须用双引号括起来,没有空格时也可以用引号,效果和不用一样;变量名只能包括大小写字母(a~z和A~Z)、数字(0~9)、下画线(_)等符号,并且变量名不能以数字开头,否则视为无效变量名,变量区分大小写。
3.变量声明和使用
要使用变量,首先要进行变量的声明,因为shell变量是弱类型的,因此不用声明变量的类型,变量声明与赋值的格式如下:
![](https://epubservercos.yuewen.com/680E1C/17977546608668706/epubprivate/OEBPS/Images/53_01.jpg?sign=1739487384-8SBvWiyUn57jKdbHsk0EfDaljEz3EJGR-0-a6a778712f7d4fa66dd2c037d0d4e962)
变量一旦声明和赋值完成,就可以进行引用了,变量引用的方法有两种:
![](https://epubservercos.yuewen.com/680E1C/17977546608668706/epubprivate/OEBPS/Images/53_02.jpg?sign=1739487384-vOTJCWH2cBklBalPBamFdmeoI83IflDi-0-f3f1039720764bb37dc4203ca05f32ba)
两种引用方法,在不同环境可进行不同选择。一般规则是:如果变量名为一个字符时建议使用方式一,多于一个字符时建议使用方式二。例如,$a、${abc}。
要显示变量,可以通过echo命令。echo命令可以显示单个变量取值,变量名前加$即可,例如:
![](https://epubservercos.yuewen.com/680E1C/17977546608668706/epubprivate/OEBPS/Images/53_03.jpg?sign=1739487384-SrdoD11wk070KOzoFHXwZ3KNrj3LlpkH-0-9d187f7fd027e4514ab854b8478827da)
这里仍然建议,输出引用变量时加{}。如果变量名多于一个字符,不加大括号可能会引起不必要的错误。
4.变量清除与只读
当变量不再使用时,可以通过unset命令进行清除,unset命令清除变量的格式为:
![](https://epubservercos.yuewen.com/680E1C/17977546608668706/epubprivate/OEBPS/Images/53_04.jpg?sign=1739487384-Qshc5ma6jVPI05tRPTIyy5HPFN322dME-0-b11b80ede2961e13e6f91a8db64bba72)
有时想让某个变量变成只读,变量一旦设置为只读,任何用户不能对此变量进行重新赋值,此时可以使用readonly命令,设置变量只读格式如下:
![](https://epubservercos.yuewen.com/680E1C/17977546608668706/epubprivate/OEBPS/Images/53_05.jpg?sign=1739487384-fg6YC7uremxbhdSv32einBlYQVThNK1K-0-81a70c707679bf8609f3a25ee849daec)
下面来看个例子:
![](https://epubservercos.yuewen.com/680E1C/17977546608668706/epubprivate/OEBPS/Images/53_06.jpg?sign=1739487384-RA9NdGJ1iKxuTfW7NxNiPSLwJKGbgKPV-0-17fc4893fb702f130648c9336a375130)
可以看出,变量只读后,无法清除和重新赋值。
5.内部参数变量
内部参数分两类,一类是命令行参数相关的,见表2-2。
表2-2 内部参数变量与含义
![](https://epubservercos.yuewen.com/680E1C/17977546608668706/epubprivate/OEBPS/Images/54_01.jpg?sign=1739487384-WvyKm8G1gW80tbjz9iet1HJHwKUtVfTO-0-3664879a52c01d2c03039a7868451ac3)
这里有两个变量需要注意:$*和$@都表示传递给脚本或函数的所有参数,但不被双引号(" ")包含时,都以$1、$2、…、$n的形式输出所有参数。但是当它们被双引号("")包含时,$*会将所有的参数作为一个整体,以$1$2 … $n的形式输出所有参数;而$@会将各个参数分开,以$1、$2、…、$n的形式输出所有参数。
另一类内部参数是与进程状态相关的,常见的参数见表2-3。
表2-3 与进程相关的内部参数与含义
![](https://epubservercos.yuewen.com/680E1C/17977546608668706/epubprivate/OEBPS/Images/54_02.jpg?sign=1739487384-w9bZcixMMqAZgfGUpuNgBsSKRGqkZ1Rl-0-2f29a692eca0b8ee0ee60a8015414a20)
下面看一个例子myscript1.sh,脚本内容如下:
![](https://epubservercos.yuewen.com/680E1C/17977546608668706/epubprivate/OEBPS/Images/54_03.jpg?sign=1739487384-IIbuudLdhXp5Wvotd4xaRfligF3WBctH-0-0e85ba8e1f24411d3adc062b89636532)
这个例子集中演示了位置参数变量、内部参数变量的含义和输出。执行脚本,后面跟上对应的参数,即可看到对应的不同变量的输出。操作如下:
![](https://epubservercos.yuewen.com/680E1C/17977546608668706/epubprivate/OEBPS/Images/54_04.jpg?sign=1739487384-nB11Ib628rr5HsJdusUel1lhN1U0m57R-0-f7a6ec5dec5ce424241779d3c78ac00c)
![](https://epubservercos.yuewen.com/680E1C/17977546608668706/epubprivate/OEBPS/Images/55_01.jpg?sign=1739487384-0a6rzcF2tJhSXMwHMvDysZeWdH3DhA7S-0-b5766e3333eae8369e0d8b6a3bc1c49a)
再来介绍一个IFS变量,shell脚本中有个变量叫IFS(Internal Field Seprator),内部域分隔符,IFS的默认值为空白(包括空格、Tab和新行),例如:
![](https://epubservercos.yuewen.com/680E1C/17977546608668706/epubprivate/OEBPS/Images/55_02.jpg?sign=1739487384-EI41zyIv1xz16Gj8Wq80LIsRJ1gUnLqc-0-7cef967b08ae497a5809ac4b531a0018)
直接输出IFS是看不到的,把IFS转化为八进制就可以看到了,040是空格,011是Tab,012是换行符\n。最后一个012是因为echo默认是会换行的。
这里再来总结下$*和$@的区别,$*会根据IFS的不同来组合值,而$@则会将值用空格来组合值,推荐使用$@,而不是$*。
下面给出一个脚本myscript2.sh,通过输出就能看到它们的差别:
![](https://epubservercos.yuewen.com/680E1C/17977546608668706/epubprivate/OEBPS/Images/55_03.jpg?sign=1739487384-Sum25AfsPmWHz9xyBmHk1v6y17RmjAHM-0-1bd7fa3df7ec9ab936f1de09afb61d9d)
执行上面脚本,输出如下:
![](https://epubservercos.yuewen.com/680E1C/17977546608668706/epubprivate/OEBPS/Images/55_04.jpg?sign=1739487384-Ydd2NiYe1ZD2wr4fn7ZL1812OAGOVg6w-0-f91d894ebe59dcb8a2cb43548ceb01a7)
从这个脚本的执行结果,可以看出$@、$*以及与IFS的关系。
6.位置参数变量
位置参数是一种特殊的shell变量,用于从命令行向shell脚本传递参数。
$1表示第1个参数、$2表示第2个参数等,$0表示脚本的名字,从${10}开始,参数号需要用大括号括起来,如${10}、${11}、${100}等。那么位置参数主要用在什么地方呢?常用的环境有两个:退出/返回从shell命令/脚本的命令行接受参数或在调用shell函数时为其传递参数。
7.退出/返回状态
shell中有多种退出/返回状态,在写shell脚本的时候经常用到这些状态,那么如何获取脚本的退出/返回状态呢?可以通过$?来实现。$?用来退出/返回上一条语句或脚本执行的状态,常见状态如下:
![](https://epubservercos.yuewen.com/680E1C/17977546608668706/epubprivate/OEBPS/Images/56_01.jpg?sign=1739487384-HThZDqsTGMgyWONvCecCyHwo43GQuegd-0-7862e0761898e275e0f2fbc8e3bd71c8)
其实可以在shell脚本中设置退出/返回状态,通过exit命令来实现。exit命令用于退出/返回脚本或当前shell,在退出/返回的时候,可以设置退出/返回状态码,方法如下:
![](https://epubservercos.yuewen.com/680E1C/17977546608668706/epubprivate/OEBPS/Images/56_02.jpg?sign=1739487384-eYYaLlCvo0PBWhFAWaB0se1SVYzmkgkx-0-9278152c9fb538abb8dd8cd1d498f59b)
其中,n是一个从0~255的整数,0表示成功退出/返回,非零表示遇到某种失败,返回值被保存在状态变量$?中。常见的退出/返回状态码见表2-4。
表2-4 常见的退出/返回状态码与含义
![](https://epubservercos.yuewen.com/680E1C/17977546608668706/epubprivate/OEBPS/Images/56_03.jpg?sign=1739487384-7JIo5H4JANRyJVsuKAakB4oBzunGG56w-0-d719757fe2df771c1f36bdc5d9bdf7a0)
看下面几个例子:
![](https://epubservercos.yuewen.com/680E1C/17977546608668706/epubprivate/OEBPS/Images/56_04.jpg?sign=1739487384-W8ylTSYhBLmw5EHBztr5p8dz82Gw7owG-0-e0aad719bba9fbdfa368ea28c3b3ce97)
![](https://epubservercos.yuewen.com/680E1C/17977546608668706/epubprivate/OEBPS/Images/57_01.jpg?sign=1739487384-dx9YfnJVc3rsyEeh8RcI707Zz4w39yqM-0-b2d8a5e6d60bdd3baffbeed73983d111)
通过命令的执行和输出,可以看出,每个退出/返回状态码的含义,明白了这些退出/返回状态码,就可以在写shell的时候进行调用,以判断命令是否执行成功。
8.命令替换
命令替换是指将命令的输出作为命令替换位置的文本,命令替换的作用是抽取一个命令的输出,然后使用=操作赋值到一个变量供以后使用。命令替换在shell编程中经常用到,有两种使用格式,分别是:
![](https://epubservercos.yuewen.com/680E1C/17977546608668706/epubprivate/OEBPS/Images/57_02.jpg?sign=1739487384-lkSuPpBJzfV4rrYtTX6l7KAaApNz4X6M-0-1080df3e520bf5a4e74fca451a778d93)
注意是反引号,也就是键盘〈Esc〉下面的那个键,看下面的例子:
![](https://epubservercos.yuewen.com/680E1C/17977546608668706/epubprivate/OEBPS/Images/57_03.jpg?sign=1739487384-tkhFIIJYWuyU4c6oZWqd1y5KAIPdXkBx-0-6b687225b6efe2c16fd31eb1933400d3)
两个例子都是将Nginx的进程数统计出来,然后赋给httpnum和httpnum1变量,在shell脚本中,变量这样定义后,下面就可以直接引用了。两种方式各有优缺点,推荐使用第2种方式。
9.read命令
read命令用来接收键盘输入内容为变量赋值,具体用法如下:
![](https://epubservercos.yuewen.com/680E1C/17977546608668706/epubprivate/OEBPS/Images/57_04.jpg?sign=1739487384-TMDez6HANrdOefaObUzNWz8B7GNrjYtP-0-24c70bf0ee45ab89d6e9255a73be101e)
若省略变量名,则会将输入的内容存入默认REPLY变量中。看下面例子:
![](https://epubservercos.yuewen.com/680E1C/17977546608668706/epubprivate/OEBPS/Images/57_05.jpg?sign=1739487384-pkDvkfVUse0AtstnKwOcDCyIkymrLQxQ-0-96e93dad6bdf18971e7ea864cb760569)
可以结合不同的引号为变量赋值,规则如下所述。
➢ 双引号"":允许通过$符号引用其他变量值。
➢ 单引号'':禁止引用其他变量值,$视为普通字符,因此引用变量时不要用单引号。
➢ 反撇号``:将命令执行的结果输出给变量。
看下面这个shell脚本myscript3.sh:
![](https://epubservercos.yuewen.com/680E1C/17977546608668706/epubprivate/OEBPS/Images/58_01.jpg?sign=1739487384-HBKz1X5XTwck3cI8CVrLXrZrulwHpyMn-0-6bd245e7f11298ec72a552d1156b20c1)
最后来执行脚本看看输出结果:
![](https://epubservercos.yuewen.com/680E1C/17977546608668706/epubprivate/OEBPS/Images/58_02.jpg?sign=1739487384-4FXvKuxuniORmSXcNN33wHRDiH6CxlxU-0-278e001ea938eeceb25df9b8ecabd872)
2.1.3 变量测试、截取与替换
1.变量测试的用法
shell支持变量测试和默认赋值,当一个变量不存在的时候,可以默认给此变量进行赋值。变量测试和赋值有多种方式,常见的有4种情况,见表2-5。
表2-5 变量测试的几种用法与含义
![](https://epubservercos.yuewen.com/680E1C/17977546608668706/epubprivate/OEBPS/Images/58_03.jpg?sign=1739487384-PnGCNAtQpSMK8cFKBJW6ZhDzE0ABFXnW-0-3f7d59a641c23dcf771afc6069dc601f)
看下面这个操作过程,更能清晰地理解每个变量测试的含义:
![](https://epubservercos.yuewen.com/680E1C/17977546608668706/epubprivate/OEBPS/Images/59_01.jpg?sign=1739487384-DujKDwxVpGzHsRpXU4WTQKJiNJwp4l5b-0-33ba7271dafaa4df3f76a518c783564e)
2.字符串长度与截取
awk和sed可以进行文本中字符串的过滤、筛选和替换。其实,shell本身也支持这种功能,下面就来看看shell中字符串长度与截取的方法,见表2-6。
表2-6 字符串长度与截取的用法与含义
![](https://epubservercos.yuewen.com/680E1C/17977546608668706/epubprivate/OEBPS/Images/59_02.jpg?sign=1739487384-1sEw1FSJ0BDVvP385rf7WBoTjTlk0fv9-0-4c494d3146e4aa1d19cd18cac0bccc05)
看下面这个例子,操作过程如下:
![](https://epubservercos.yuewen.com/680E1C/17977546608668706/epubprivate/OEBPS/Images/59_03.jpg?sign=1739487384-G3GiZCNMKso02xBIYFxfjaVQNp8zAAIn-0-22298e3034f0ad88fef22852fa936a1f)
![](https://epubservercos.yuewen.com/680E1C/17977546608668706/epubprivate/OEBPS/Images/60_01.jpg?sign=1739487384-DRXijdR2cH0et6t9nuYwtCQlM3VzalLN-0-009f9929abd6b691b53b4bf5d6af57ae)
此外,shell还支持字符串替换,见表2-7。
表2-7 字符串替换用法
![](https://epubservercos.yuewen.com/680E1C/17977546608668706/epubprivate/OEBPS/Images/60_02.jpg?sign=1739487384-xIILAobAzWFJ63mU0FPMjFaix1Jfan1s-0-4963c4f418e515fcf615363a9d1f9067)
需要注意的是old中可以使用通配符。var可以是@或*,表示对每个位置参数进行替换,继续看下面的例子,操作过程如下:
![](https://epubservercos.yuewen.com/680E1C/17977546608668706/epubprivate/OEBPS/Images/60_03.jpg?sign=1739487384-nTghUjlSsadKEQ3Ao9ACU9efyVMjN2w8-0-1db9ee9aaabd89dd5cdc709af96b128a)
3.变量的间接引用
先来看一个例子:
![](https://epubservercos.yuewen.com/680E1C/17977546608668706/epubprivate/OEBPS/Images/60_04.jpg?sign=1739487384-jvehJKbbuYucR60bfbhQmysIUvjHDedE-0-b5bc58fee850657915aca724c6e3b33c)
这个例子中,想让$str2输出Hello World,那么echo $str2将输出什么呢?是否能够满足要求?显然不能,上面这个输出中,最后echo $str2会输出的值为str1,那么如何才能输出所需要的值呢?可以这样执行:
![](https://epubservercos.yuewen.com/680E1C/17977546608668706/epubprivate/OEBPS/Images/60_05.jpg?sign=1739487384-jzMQSZUScG0TuirpOYKeJNtdg8eAzQnc-0-f5f3c98baf513ae9da537121836040fd)
上面两个命令都能实现将str1的值间接的赋给str2,最后结果为Hello World,满足间接赋值要求。再来看个例子:
![](https://epubservercos.yuewen.com/680E1C/17977546608668706/epubprivate/OEBPS/Images/61_01.jpg?sign=1739487384-zXpO7qZhfR5EvL59yytVQdbnmuVtA8Zc-0-b69b7952e0ba0f4212d5414ae38fd30c)
那么看下面这几个组合输出什么内容:
![](https://epubservercos.yuewen.com/680E1C/17977546608668706/epubprivate/OEBPS/Images/61_02.jpg?sign=1739487384-FWQiSzOy2tu5VuNdKf3Az4HR7UPe2MlV-0-e266f8326ecdca186f3d8ba87a917b16)
来执行看看结果:
![](https://epubservercos.yuewen.com/680E1C/17977546608668706/epubprivate/OEBPS/Images/61_03.jpg?sign=1739487384-FYctCxWiDLMOjgRtH8Az3oZ5B7GYZyS4-0-dbe94d5de305714bb41a49992279227d)
很显然,第2个是满足需要的,通过间接引用变量,实现了变量值的替换。
4.同时输出多行信息
同时输出多行信息也经常会使用,有两种方法可以实现,第1种是使用echo命令,用法如下:
![](https://epubservercos.yuewen.com/680E1C/17977546608668706/epubprivate/OEBPS/Images/61_04.jpg?sign=1739487384-iWTCoTaYghJ67QjkGmNV31xUKmsnKu3b-0-2eb4eaf3fbfa6543cfaec94e2f154af0)
注意,多行内容中不能出现双引号,否则echo提前结束,若确实需要使用双引号,需使用转义字符\。同时输出多行信息的第2个方法是使用here file,方法如下:
![](https://epubservercos.yuewen.com/680E1C/17977546608668706/epubprivate/OEBPS/Images/61_05.jpg?sign=1739487384-TXcGpUsPuIs3y8fh2772ibZb2xuXcBQq-0-943c234ff112ca1aabba9c859c806f84)
注意:END可以是任意字符串,只要上下一致即可,多行内容中不能出现内容为END开始的行,否则cat提前结束。