计算机 · 2021年12月7日 0

Shell中的字符串处理

shell中的正则表达式

正则表达式引擎

正则表达式是用正则表达式引擎(regular expression engine)实现的。正则表达式是解释正则表达式模式并使用这些模式进行文本匹配的底层软件。
在Linux中有两种流行的正则表达式引擎:

  1. POSIX 基本正则表达式(BRE)引擎
  2. POSIX 扩展正则表达式(BRE)引擎

大多数Linux工具都至少符合POSIX BRE引擎规范,能够识别它定义的所有模式符号。但是如sed之类的工具却只实现了BRE引擎规范的子集(为了不降低处理数据的速度)。

基本正则表达式式(BRE)

正则表达式中的特殊字符:.*[]^{}\+?|()

  1. 锚字符
    • 用于锁定在行首的脱字符(^)
    • 用于锁定在行尾的美元符($)
  2. 点字符
    点字符用于匹配任意的单个字符。
  3. 字符组
    用中括号来指明可以匹配的字符范围。
  4. 排除字符组
    可以通过在字符组的开头加个脱字符来反转字符组的作用,即匹配指定字符组以外的所有字符,比如通过[^ch]来匹配ch两个字符之外的字符。
  5. 使用区间
    使用这种[0-9]区间的写法来避免把每个字符都写出来。
  6. 特殊字符组
    BRE提供了一些预定义的特殊字符组:组描述[[:alpha:]]匹配任意字母字符,不论大小写[[:alnum:]]匹配任意字母数字字符[[:blank:]]匹配空格或制表符[[:digit:]]匹配0-9之间的数字[[:lower:]]匹配小写字母字符a-z[[:print:]]匹配任意可打印字符[[:punct:]]匹配标点符号[[:space:]]匹配任意空白字符:空格、制表符、NL、FF、VT和CR[[:upper:]]匹配任意大写字母字符A-Z
  7. 星号 在字符后面放置星号表示该字符可以出现0次或多次。

扩展正则表达式

由于gawk相比sed有支持扩展正则表达式的优点和处理数据速度更慢的缺点。

  1. 问号 在字符后面放置问号表示该字符可以出现0次或者1次.
  2. 加号 在字符后面放置加号表示该字符至少出现1次。
  3. 使用花括号
    花括号为字符指定可重复次数:
    • a{m}表示a需要准确的出现m次
    • a{m, n}表示a至少出现m次,至多出现n次
  4. 管道符号
    管道符号使得在检查数据流时可以指定多个要匹配的正则表达式,只要匹配了其中的某一个正则表达式就算匹配。
  5. 聚合表达式
    前面都是以单个字符为单位,可以通过聚合表达式(圆括号)将一个单词作为一个单位来考虑。
    譬如用(Monday)?表示Monday出现0次或者1次。

一个使用正则表达式的经典例子:匹配电子邮件地址

其实这个问题没什么和正则表达式相关的难点,难点在于搞清楚电子邮件地址的具体规则。按照维基百科的说法似乎正则表达式还并不能够涵盖所有有效的电子邮件地址。

grep

  • 按照单词去匹配
    找出所有含有the的行grep '\<the\>' 或者:grep '\bthe\b' 这里其实也有个坑,比如我不经意就写成了:grep \<the\> 这种写法的话是只能匹配到<the>这个字符串的,而不是单词the。去查grep的manual,可以看到:
    Typically PATTERNS should be quoted when grep is used in a shell command. Any meta-character with special meaning may be quoted by preceding it with a backslash. 为了避免这种错误的发生,以后就都记得给grep匹配的pattern加上单引号好了。
  • 匹配时忽略大小写grep -i
  • 反向匹配
    找出不匹配的行:grep -v

sed

参考资料

  • 原教程地址
  • sed教程
  • 把所有的vermin替换为pony
    sed 's/vermin/pony/g' metamorphosis.txt > ponymorphosis.txt
  • 只替换第一个vermin为pony
    sed 's/vermin/pony/1' metamorphosis.txt > ponymorphosis.txt
    或者不要这个1也可以
    sed 's/vermin/pony/' metamorphosis.txt > ponymorphosis.txt
  • 在原文件中替换
    sed -i 's/vermin/pony/g' metamorphosis.txt
  • 替换的时候忽略大小写
    i表示忽略大小写:sed -e 's/thy/{&}/gi'
  • 按照单词进行匹配
    把单词the替换为thissed -e 's/\<the\>/this/' \<\>分别表示单词(word)的开始和结束,和vim里面的语法居然是一样的。也可以使用\b来表示单词的边界:sed -e 's/\bthe\b/this/'
  • 引用匹配到的pattern
    sed -e 's/vermin/{&}/g' metamorphosis.txt
    这会把所有的vermin替换为{vermin}
  • 关于group和back references参考这道题目,把信用卡上面的几组数字逆序排列:
    sed -e 's/\(\d\+\) \(\d\+\) \(\d\+\) \(\d\+\)/\4 \3 \2 \1/'
    其实就是用\(\)去捕获pattern,然后再用\1\2这种去引用捕获的pattern就可以了。

paste

paste命令可以把多个文本文件的内容按行给连接起来,用tab分割。

  • paste -s
    逐一地把每个文件的每一行用tab连起来,而不是按照默认的每个文件取一行的方式。
  • paste -d ';'
    用分号作为分割符而不是默认的TAB。
  • paste -d ' ' - - -
    用paste命令把一个指定文件的内容每三行合并成一行,合并的时候这三行用分号分割开。通过这个命令可以对-的用法有更清晰的认识: -代表了从标准输入中读取一行。

awk

参考教程

  • 获取列的个数
    引用变量NF就可以了:
    awk '{if (NF < 4){print "Not all scores are available for "$1}}'
  • 根据不同的情况执行不同的action
    判断学生是否通过考试
    awk '{if ($2 >= 50 && $3 >= 50 && $4 >= 50) print $1 " : Pass"; else print $1 " : Fail"}'
    注意语句是要用分号结尾的。
    更复杂一点的例子
    awk '{ total=$2+$3+$4; avg=total/3; if (avg >= 80) grade = "A"; else if (avg >= 60) grade = "B"; else if (avg >= 50) grade = "C"; else grade = "FAIL"; print $0 " : " grade}'
    这里用$0来表示输入的每一行。
  • 用awk把文件里的每两行给连起来,分割符用;
    awk 'ORS=NR%2?";":"\n"'