🔥码云GVP开源项目 12k star Uniapp+ElementUI 功能强大 支持多语言、二开方便! 广告
##shell十三问之15: [^ ] 跟[! ]差在哪? (RE: Regular Expression) ------------------------------------------------------------ ### Part-II Regular Expression (正则表达式) ------------------------------------------ 接下来的Regular Expression(RE) 可是个大题目,要讲的很多。 我这里当然不可能讲得很全。 只希望能带给大家一个基本的入门概念,就很足够了... 先来考一下英文好了:What is expression? 简单来说,就是"表达",也就是人们在沟通的时候所要陈述的内容。 然而,生活中,表达方要清楚的将意思描述清楚, 而让接收方完整无误地领会,可不是件容易的事情。 因而才会出现那么多的"误会", 真可叹句"表达不易"啊...... 同样的情形也发生在计算机的数据处理过程中, 尤其是当我们在描述一段"文字内容"的时候.... 那么,我们不禁要问: 有何方法可以让大家的误会降至最低程度, 而让表达的精确度达到最高程度呢? 答案就是"标准化"了, 也就是我们这里要谈的`Regular Expression`啦...^_^ 然而,在进入`RE`介绍之前,不妨先让我们温习一下shell十三问之第4问, 那就是关于quoting的部分。 **关键是要能够区分 shell command line上的meta与literal的这两种不同的字符类型**。 然后,我这里也跟你讲: **RE 表达式里字符也分meta与literal这两种**。 呵,不知亲爱的读者是否被我搞混乱了呢?... ^_^ 这也难怪啦,因为这的确是最容易混淆的地方, 刚学`RE`的朋友很多时候,都死在这里! 因此,请特别小心理解哦... 简单而言,除非你将`RE`写在特定程序使用的脚本里, 否则,我们的`RE`也是通过 command line输入的。 然而, **不少RE所使用的meta字符,跟shell 的meta字符是冲突的**。 比方说, **`*`这个字符,在RE里是一个modifier(修饰符);而在command line上,确是wildcard(通配符)**。 那么,我们该如何解决这样的冲突呢? 关键就是看你对shell十三问的第4问中所提的quoting是否足够理解了! 若你明白到 **shell quoting 就是用来在command line上关闭shell meta这一基本原理**, 那你就能很轻松的解决 RE meta与shell meta的冲突问题了: **用shell quoting 关闭掉shell meta就是了**。 就这么简单... ^_^ 再以刚提到`*`字符为例, 若在command line的path中没有quoting处理的话, 如abc\* 就会被作为wildcard expression来扩充及重组了。 若将其置于quoting中,即"abc\*",则可以避免wildcard expand的处理。 好了,说了大半天,还没有进入正式的RE介绍呢.... 大家别急,因为我的教学风格就是要先建立基础,循序渐进的... ^_^ 因此, 我这里还要再啰嗦一个观念,才会到RE的说明啦...(哈...别打我...) 当我们在谈到RE时,千万别跟wildcard搞混在一起! 尤其是 ``` 在command line的位置里,wildcard只作用于argument的path上; 而RE却只用于"字符串处理" 的程序中,这与路径名一点关系也没有。 ``` > **Tips:** > RE 所处理的字符串,通常是指纯文本或通过stdin读进的内容。 okay,够了够了,我已看到一堆人开始出现不耐烦的样子了... ^_^ 现在,就让我们登堂入室,揭开RE的神秘面纱吧, 这样可以放过我了吧? 哈哈... 在RE的表达式里,主要分为两种字符:`literal`与`meta`。 所谓`literal`就是在RE里不具有特殊功能的字符,如abc,123等; 而`meta`,在RE里具有特殊的功能。 要关闭之,需要在`meta`之前使用escape(\)转义字符。 然而,在介绍`meta`之前,先让我们来认识一下字符组合(character set)会更好些。 一、所谓的char set就是将多个连续的字符作为一个集合。 例如: | char set | 意义 |-------------|---------------------------- | abc | 表示abc三个连续的字符,但彼此独立而非集合。(可简单视为三个char set)| | (abc) | 表示abc这三个连续字符的集合。(可简单视为一个char set)| | abc\|xyz | 表示abc或xyz这两个char set之一| |[abc] | 表示单一字符,可为a或b或c;与wildcard的[abc]原理相同,称之为字符类。| |[^abc] |表示单一字符,不为a或b或c即可。(与wildcard [!abc]原理相同)| | . | 表示任意单个字符,(与wildcard的?原理相同)| note: abc|xyz 表示abc或xyz这两个char set之一 在认识了RE的char set这个概念之后,然后,在让我们多认识几个RE中常见的meta字符: 二、 锚点(anchor): 用以标识RE在句子中的位置所在。 常见的有: |锚点 | 说明| |-------|-------| | ^ | 表示句首。如,^abc表示以abc开头的句子。| | $ | 表示句尾。如,abc$表示以abc结尾的句子。| | \\< | 表示词首。如,\\<abc表示以abc开头的词。| | \\> | 表示词尾。如,abc\\>表示以abc结尾的词。| 三、 修饰符(modifier):独立表示时本身不具意义,专门用以修饰前一个char set出现的次数。 常见的有: | modifier | 说明| |------------|----------------------------------- | * | 表示前一个char set出现0次或多次,即任意次。如ab*c表示a与c之间可以有0个或多个b。| | ? | 表示前一个char set出现0次或1次,即至多出现1次。如ab?c 表示a与c之间可以有0个或1个b。| | + | 表示前一个char set出现1次或多次,即至少出现1次。如ab+c 表示a与c之间可以有1个或多个b。| | {n} | 表示前一个char set出现n次。如ab{n}c 表示a与c之间可以有n个b。| | {n, } | 表示前一个char set至少出现n次。如ab{n}c 表示a与c之间至少有n个b。| | {n, m} | 表示前一个char set至少出现n次,至多出现m次。如ab{n,m}c 表示a与c之间至少有n个b,至多有m个b。| 然而,当我们在识别modifier时,却很容易忽略"边界(boundary)字符"的重要性。 以`ab{3,5}c`为例,这里的a与c就是边界字符了。 若没有边界字符的帮忙,我们很容易做出错误的解读。 比方说: 我们用`ab{3,5}`这个RE(少了c这个边界字符) 可以抓到"abbbbbbbbbb"(a后面有10个b)的字符串吗? 从刚才的modifier的说明,我们一般认为,我们要的b是3到5个, 若超出了此范围,就不是我们所要表达的。 因此,我们或许会很轻率地认为这个RE抓不到结果(上述"abbbbbbbbbb"字符串)。 然而,答案却是可以的!为什么呢? 让我们重新解读`ab{3,5}`这个RE看看: 我们要表达的是a后接3到5个b即可,但3到5个b后面,我们却没有规定什么, 因此,在RE后面可以是任意的字符串,当然包括b也可以啦!(明白了吗?) 同样,我们用`b{3,5}c`也同样可以抓到"abbbbbbbbbbc" 这样的字符串。 但当我们用`ab{3,5}c`这样的RE时, 由于同时有a与c这连个边界字符,就截然不同了! 有空在思考一下,为何我们用下面这些RE都抓到abc这样的字符串呢? ``` x* ax*, abx*, ax*b abcx*, abx*c, ax*bc bx*c, bcx*, x*bc ``` 但, 若我们在这些RE前后分别加`^`与`$`这样的anchor,那又如何呢? 刚学RE时,只要能掌握上面这些基本的meta的大概就可以入门了。 一如前述,RE是一种规范化的文字表达式, 主要用于某些文字处理工具之间,如: grep, perl, vi,awk,sed,等等, 常用于表示一段连续的字符串,查找和替换。 然而每种工具对RE表达式的具体解读或有一些细微差别, 不过节本原理还是一致的。 只要掌握RE的基本原理,那就一理通百理了, 只是在实践时,稍加变通即可。 比方以grep来说, 在Linux上,你可以找到grep,egrep,fgrep这些程序, 其差异大致如下: grep: 传统的grep程序,在没有任何选项(options)的情况下,只输出符合RE字串的句子, 其常见的选项如下: | 选项 (option)| 用途| | -----|---------------------------- | -v | 反模式, 只输出“不含”RE的字符串的行。| | -r | 递归模式,可同时处理所有层级的子目录里的文件| | -q | 静默模式,不输出任何结果(stderr 除外,常用于获取return value,符合为true,否则,为false.| | -i | 忽略大小写| | -w | 整词匹配,类似 \<RE\>| | -n | 同时输出行号| | -l | 输出匹配RE的文件名| | -o | 只输出匹配RE的字符串。(gnu新版独有,不见得所有版本支持)| | -E | 切换为egrep| egrep:为grep的扩充版本,改良了许多传统grep不能或者不便的操作, - grep下不支持`?`与`+`这两种meta,但egrep支持; - grep 不支持`a|b`或(`abc|xyz`)这类“或一”的匹配,但egrep支持; - grep 在处理`{n,m}`时,需要\\{ 与 \\}处理,但egrep不需。 等诸如此类的。我个人建议能用egrep就不用grep啦...^_^ fgrep: 不作RE处理,表达式仅作一般的字符串处理,所有的meta均市区功能。 好了,关于RE的入门,我们暂时就介绍到这里。 虽然有点乱,且有些观念也不恨精确, 不过,姑且算是对大家的一个交差吧...^_^ 若这两天有时间的话,我在举些范例来分析一下,以帮助大家更好的理解。 假如更有可能的话,也顺道为大家介绍一下sed这个工具。 --------------------------------------- ### Part-III eval --------------------------------------- 讲到command line的重组特性, 真的需要我们好好的加以解释的。 如此便能抽丝剥茧的一层层的将整个command line分析的 一清二楚,而不至于含糊。 假如这个重组的特性理解了,那我们介绍一个好玩的命令:`eval`. 我们在变量替换的过程中,常会碰到所谓的复式变量的问题: 如: ```shell a=1 A1=abc ``` 我们都知道`echo $A1`就可以得到abc的结果。 然而,我们能否用$A$a来取代$A1,而同一样替换为abc呢? 这个问题我们可用很轻松的用`eval`来解决: ```shell eval echo \$A$a ``` 说穿了,`eval` 只不过是在命令行完成替换重组后, 在来一次替换重组罢了... 就是这么简单啦~~~ ^_^