Shell字符串处理命令 - baibing0716/C- GitHub Wiki

一、正则表达式

(1)目的:对文本(字符串)进行过滤、替换(编辑)、格式化等  regular expression

 (2) 正则表达式: pattern = 特殊字符 + 文本字符 

 (3) 正则表达式分类: Basic RE    Extend RE

(1) 匹配字符的(单个字符、字符集合、字符范围、字符间的选择)

(2) 匹配次数

(3) 位置锚定

位置锚定: 
          ^           用于pattern的最左侧,以pattern开头的行
          $           用于pattern的最右侧,以pattern结尾的行
          ^$          组合符 表示空行

匹配字符:
         [abcd]       匹配[]内任意一个字符等价于===[a-d]
         [^abcd]      ^在[]内表示取反,匹配除了[]内的所有字符
         .            通配符 匹配除换行符 \n 之外的任何单字符
         *            通配符所有内容
         .*           组合符 匹配所有内容
         ^.*          组合符 匹配以任意多个字符开头的内容
         .*$          组合符 匹配以任意多个字符结尾的内容

匹配次数:
        {n}           匹配出现n次
        {n,}          至少匹配出现n次
        {n, m}        在[n,m]区间次
        +             >= 1次 等价于 {1,}
        ?            <= 1次 等价于 {0, 1}

示例:
     ^[0-9a-z_-]+{3,15}abc$

## ^为匹配输入字符串的开始位置

## 0-9 a-z _-组合的字符串长度必须在3-15这个区间

## abc$匹配字母 abc 并以 abc 结尾,$ 为匹配输入字符串的结束位置。

二、文本处理命令

1. cut命令-----针对文件或管道----按行处理

测试文本 cut.txt
a_202204110214_10.251.82.225
b_202204110215_10.251.82.223
b_202204110212_10.251.82.226
c_202204110214_10.251.82.228

# 截取第一个字符 第三个字符 a2 b2 b2 c2
cut -b 1,3 cut.txt        ## 1,3 表示的是第一个字符、第三个字符 【-b 以字节为单位截取 -c 以字符为单位进行截取】

# 截取3-6这段字符串 2022 2022 2022 2022
cut -b 3-6 cut.txt  

# 按分割符划分成几个section 配合-f 取第几个section内容
cut -d _ -f 3 cut.txt    ## -d _划分为3个section 取第3个section内容 输出结果:10.251.82.225 10.251.82.223 10.251.82.226 10.251.82.228

2. sort命令

#测试文本
AAA:2:13
BBB:80:14
CCC:13:18
DDD:45:11                   # 与
EEE:30:17
DDD:45:11

sort -u  sort.txt           # 排序后删除重复的行
                            
sort -nk 2 -t : sort.txt    # -t 排序指定的分隔符 : -n 根据字符串的数字比较(不需要参数) -k 指定需要排序的栏位(列、域)

#测试文本
192.168.0.13
192.168.0.12
23.207.92.222
129.204.222.143
114.114.114.114
114.111.112.114

sort -k1,1n  -k2,2n  -k3,3n  -k4,4n  sort.txt    # 对第一列、第二列、第三列、第四列进行排序(规则 越前等级越高)

sort的其它选项
-b       忽略每行前的空白区域
-d       只考虑处理空格、字母、数字
-f       忽略字母大小写
-m       合并已经排序的文件 不排序
-o       导出排序结果到文件中

3. uniq去重命令(只能去除相邻的重复行)

# 测试文本 uniq.txt
dong dong
lai lai
dong dong
shun shun
shun shun
fan fan
fan fan
dian dian


(1) uniq -u uniq.txt            # 只显示出现一次的行
输出结果:
dong dong                       # 不相邻的重复的行 认为是两行
lai lai
dong dong
dian dian

(2)uniq -d uniq.txt            # 只显示(相邻)重复的行
输出结果:
shun shun
fan fan

(3)uniq -c uniq.txt           # 删除文件中相邻的重复的行 And 显示每行出现的次数
输出结果:
1 dong dong
1 lai lai
1 dong dong
2 shun shun
2 fan fan
1 dian dian

(4)uniq uniq.txt             # 不加参数的选项后的输出
dong dong
lai lai
dong dong
shun shun
fan fan
dian dian

4. wc统计命令

wc 统计文件的行数、字节数、单词数
#测试文本 wc.txt
dong dong
lai lai
shun shun
fan fan
dian dian

#不加参数选项
(1) wc wc.txt
 5 10 46 cut2.txt           # 行数 字数 字节数 文件名

(2) wc -l wc.txt
 5 wc.txt                  # -l 行数

(3) wc -w wc.txt
 10 wc.txt                 # -w 字数

(4) wc -c wc.txt
 46 wc.txt                 # -c 字节数    

5. tr 转换或删除文件中的字符

-c  --complement 反选设定字符,选择的不处理,不选的处理

-d  删除指令的指定的字符

-s  将连续的重复的字符缩进成单个字符         baaaaaac ==>  bac

(1) 替换字符

echo 'Hello World' | tr 'H' 'h'           # 输出 hello World  

echo 'Hello World' | tr 'Ho' 'xx'         # 输出 xellx Wxrld        将H字符替换为x o字符替换成x

(2) 删除字符

echo 'Hello World 12345' | tr -cd [:digit:]  # -c反选 -d 删除 [:digit:] 数字集 即删除除数字外其它字符   输出  12345

echo 'Hello World 12345' | tr -cd [:alpha:]  # -c反选 -d 删除 [:alpha:] 字母集 即删除除字母外其它字符   输出  HelloWorld

(3) 压缩字符

echo 'HHHHHHHHelloooooooooooo Woooooooorrrrrrlddd' | tr -s 'Hord'      # 压缩的字符是H o r d 输出: Hello World

6. 文本处理三剑客grep sed awk

6.1 文本过滤 grep

grep本身是按行输出

格式: 
     grep [选项] [pattern] file

选项:
     -E   支持扩展正则

匹配控制选项:
     -i   忽略大小写
     -v   反转匹配的pattern

输出选项
     -o   只输出查找pattern
     -n   输出行号
     -c   打印匹配的行数
     -l   输出匹配的每一行

示例:
grep -rn "hello,world" /home/itcast                   查找 /home/itcast 下包含“hello,world“字符串的文件
grep -a hello /bin/ls                                  将二进制文件以文本文件的方式搜索 hello 
grep -i hello /etc/passwd                              在 /etc/passwd 文件里找 hello 并且忽略大小写查找 
grep -n hello /etc/passwd                              搜索 hello 结果并显示在文件里出现的行号 
grep -w hello /etc/passwd                              搜索完全匹配 hello 单词的行 
grep -v hello /etc/passwd                              显示出在 /etc/passwd 文件里没有 hello 的行
grep -r hello /etc/                                    在 /etc/ 目录里所有文件里找 hello 并显示结果
grep -i hello /etc/passwd --color=auto 在/etc/passwd   文件中找 hello 并且忽略大小写,然后高亮显示匹配的关键字

6.2 文本编辑 sed

sed stream editor  对文本进行增删改查(过滤字符串 取出指定行)

sed的工作原理: 从文件中按行读取以后 加载到sed的模式空间 看是否符合pattern 
               不符合pattern则不输出到屏幕 符合pattern,处理然后输出屏幕
               源文件单行--->sed模式空间--->pattern--->结果是否符合预期
sed格式:
            sed [选项]  [sed内置命令字符] [输入文件]
选项:
            -n    通常与sed内置p结合使用 不匹配的不输出了
            -i    修改的结果写入src文件, 如果不加-i修改的是sed本身的缓存
            -e    多次编辑,不需要管道符了
            -r    支持RE

sed内置命令:
            a     append在指定行后追加
            d     delete删除指定行
            i     insert在指定行插入一行或多行文本
            p     print查看指定行
            s/pattern/替换内容/g 匹配pattern然后全局替换为想要替换的内容

sed的匹配范围:
           空地址         全文处理
           单地址         指定文件某一行
           /pattern/     符合pattern的每一行  /pattern1/, /pattern2/ 可以串联
           范围区间       10,20 表示10到20行 10,+5 表示10到15行
           指定搜索步长    1~2 表示从第1行开始 步长为2的行 如 1 3 5 7 9 2~2 表示从第2行开始,步长为2的行 如 2 4 6 8 10

# 测试文本 luffcity.txt
My name is change.
I teach Linux.
I Like play computer game.
My website is http://www.pythondoc.com.cn.
My sister is reading high school.

// sed查看
sed -n "2,4p" luffcity.txt            ## 查看2-4行的文本
sed -n "2p"   luffcity.txt            ## 只查看第2行的文本

// sed查看 匹配/pattern/的行内容
sed -n "/linux/p" luffcity.txt         

// sed 删除 包含game的行
sed -n "/game/d" luffcity.txt         ## 只是删除了sed缓冲区的内容,并未对源文件进行修改
sed -n -i "/game/d" luffcity.txt      ## 必须加 -i 如果想对源文件修改必须加该选项
sed -n -i "5,$d" luffcity.txt         ## 把源文件从第5行开始到$(表示最后一行) d(sed内置命令) 全部删除
// sed 替换 模式
sed "s/你想找的内容/你想替换的内容/g"    全部替换  sed "s/pattern/替换内容/g"
sed "s#你想找的内容#你想替换的内容#g"    同上      (1)sed "s###"  (2)sed "s@@@"  (3) sed "s///"都可以
sed "s@你想找的内容@你想替换的内容@g"    同上 

sed "行范围s/旧串/新串/g"               局部替换 按照行范围

// sed 替换源文件My 为 I
sed -i "s/My/I/g" luffcity.txt          # -i 修改源文件  s///替换模式

// sed 多次编辑一个文件 把My换为I 把Linux替换Windows
sed -i -e "s/My/I/g" -e "/s/Linux/Windows/g" luffcity.txt

// sed 插入和追加 行
sed -i "2a This is new add line1" luffcity.txt      ## 在第2行后追加(新行为第3行)
sed -i "4i This is new add line2" luffcity.txt      ## 在第4行前追加(新行为第4行)
sed -i "2a Hello world\n Welcome here!"             ## 在第2行追加2行
sed -i "2a\     This is new add line3" luffcity.txt ## 在第3行插入固定格式前有空格语句This is new add line3

// sed 空地址 表示全范围
sed -i "a --------" luffcity.txt                    ## 在sed内置命令a后空格 表示空地址 在每一行后面都添加分隔符 --------

6.3 文本格式化 awk

6.3.1 awk的描述和作用

awk作用: 模式扫描与文本处理语言

awk本身是一门语言,mawk是其awk的解释器;主要针对文本数据处理、检索等

awk的程序:【pattern{action}】序列对和【函数】构成。在命令行中输入短程序,通常用{ }括起来,以避免shell解释。

awk的数据类型: 数字、字符串 

(1)awk [-F] "[分隔符]" '{print$1, $NF}' [目标文件]

(2)awk 'BEGIN {
    FS="[列分隔符]+";
    RS="[行分隔符]+";
    print "-GEGIN-"}                            # begin部分
NR == n{动作}                                   # NR部分
END {print "-END-"}' 目标文件                   # End部分          【以上三部分都包裹在' '中】

6.3.2 awk的参数选项

----------------------------常用选项----------------------------------
-F      指定分隔符的值

-f      输入从文本文件中获取,而不是从命令行读取。允许多个-f选项

-v      var=value为程序变量var赋值
----------------------------常用选项----------------------------------

----------------------------不常用选项----------------------------------
-W version       mawk将其版本和版权写入stdout,并将编译限制写入stderr并退出0。                # 用法: awk -W version

-W dump         将程序内部表示的列表等汇编程序写入stdout,并退出0(成功编译时)。

-W交互式设置     对stdout的无缓冲写入和对stdin的行缓冲读取。来自stdin的记录是行,而不管RS的值如何。

-W执行文件       从文件读取程序文本,这是最后一个选项。在支持可执行脚本的#!“魔术数字”约定的系统上很有用。

-W sprintf=num   将mawk内部sprintf缓冲区的大小调整为num字节。此选项的使用非常罕见,表明应重新编译mawk。

-W posix_space   强制mawk不将'\n'视为空间。

短格式-W[vdisp]被识别,在某些系统上-我们必须避免命令行长度限制。
----------------------------不常用选项----------------------------------

6.3.3 语法规则

主旋律: awk是写程序; 程序的结构是 pattern{action}序列对 以及 函数构成

pattern:  (1) BEGIN  (2) END  (3) 表达式   其中之一

action:  比较灵活(变量赋值、语句集)

pattern{action}序列对: 两者必须存在一个;       [BEGIN] {action} [END]  即省略pattern情况 隐式添加BEGIN END      pattern[print] 即省略{action}隐式添加print

awk语句结束: \n 或 ;

空行语句: 也要以;结束

注释: # 

6.3.4 表达式规则

在awk语言中,记录、字段、字符串通常被测试以匹配正则表达式

expr ~ /正则表达式/                                                 # 是一个awk表达式,若匹配则返回1,不匹配返回0  expr ~// 匹配所有的空行

expr !~/正则表达式/                                                 # 是一个awk表达式,若不匹配则返回1,匹配返回0 (取反上面的操作)

expr ~ /正则表达式/              <===>       expr $0~/正则表达式/    # 两者是等价写法 (特殊情况:【匹配运算符】 或 【内置函数作为正则表达式参数】两者不等价)

示例:  /^[_a-zA-Z][_a-zA-Z0-9]*$/  and                             # AWK标识符和AWK的数字常量匹配
        /^[-+]?([0-9]+\.?|\.[0-9])[0-9]*([eE][-+]?[0-9]+)?$/        # 小数点的匹配必须经过转义

        BEGIN { identifier = "[_a-zA-Z][_a-zA-Z0-9]*" }             # 任何表达式都可以在~或!的右边使用!~运算符或传递给需要正则表达式的内置运算符

        $0 ~ "^" identifier                                         # 打印以标识符开头的所有行

6.3.5 记录和字段划分

记录: 一次读取一个记录,并存储在字段变量$0中;                         # $0表示整条记录

字段: 整条记录被划分成n个字段                                        # $1 、$2、$3 、......表示第一、二、三...个字段 (字段间以FS分割)

6.3.6 内建变量、内建函数

--------------------------------------内建变量--------------------------------------------
ARGC         命令行参数的ARGC数

ARGV         命令行参数的ARGV数组

ENVIRON      由环境变量索引的环境数组;   环境字符串,var=value存储为ENVIRON[var] = value;

FILENAME     当前输入文件的FILENAME名称

FNR          FILENAME中的FNR当前记录编号(有多少行记录)

FS           拆分记录的分隔符                               【列的分隔符】

NF           当前记录有多少个(number of fileds)字段        【字段】    $NF标识最后一列

NR           总输入流中的NR                                 【行号】 

OFMT         用于打印号码的OFMT格式;初始=“%.6g”

OFS          OFS会在输出的字段之间添加=,初始=“”

ORS          ORS终止输出上的每个记录,最初=“\n”

RLENGTH      最后一次调用内置函数设置的RLENGTH长度,匹配()

RS           RS输入记录分隔符,初始=“\n”                     【行的分隔符】

RSTART       由最后一个调用设置的RSTART索引匹配()

SUBSEP       用于构建多个数组下标的SUBSEP,最初=“\034”
--------------------------------------内建变量--------------------------------------------
--------------------------------------内建函数--------------------------------------------
字符串处理函数:
(1) 全局替换函数  gsub(r,s,t) 或 gsub(r,s)             # r被s替换

      替换单条记录   sub(r,s,t) 或 sub(r,s)

(2) 查找子串索引  index(s,t)                           # s主串 t子串 s串的index从1开始计数  返回 0 失败 成功 返回对应位置

(3) 字符串长度    length(s)                      

(4) 字符串匹配    match(s, r)

(5) 拆分字符串    split(s, A, r) 或 split(s, A)        # 拆分字符串s按照正则表达式r或者默认的分隔符拆分,将拆分后的字段存在A数组中

(6) 格式化字符串  sprintf(格式, 表达式)

(7) 截取字符串    substr(s, i, n) 或 substr(s, i);     # i起始index n表示长度

(8) 字符串转化为大小写  tolower(s) 或 toupper(s)

数字处理函数:
(1) cos(x)、sin(x)

 (2)  exp(x)      指数函数

(3) int(x)      返回被截断为零的x

(4) log(x)      自然对数

(5) rand()      返回0 - 1 随机数

(6) sqrt(x)     x的平方根
--------------------------------------内建函数--------------------------------------------

五、字符串处理

5.1 主串截取指定长度或指定子串的

# -----------------------------------------------------------------------
#(1) 格式:从指定index截取固定长度

# ${string:start:length}

str_time="2022-02-01 14:36:58"


date_no=${str_time:0:10}           # 截取前10个字符             2022-02-01

date_mon=${str_time:5:2}           # 从第6位开始截取2位字符     02

date_time=${str_time:0-8}          # 从后面截取8位字符          14:36:58

date_hh=${str_time:0-8:2}          # 从后面第8位开始截取2个字符 14

echo "date_no = $date_no"
echo "date_mon = $date_mon"
echo "date_time = $date_time"
echo "date_hh = $date_hh"


teststr='/app/logs/133.38.112.177_202204111015.log'


#(2) 截取指定模式串左边的字符    

    # 格式1:${string%substr*}          
    # 格式2: ${string%%substr*}         

echo "${teststr%11*}"                    # 打印 /app/logs/133.38.112.11_2022041

echo "${teststr%%11*}"                   # 打印 /app/logs/133.38.


#(3) 截取指定主串中模式串右边字符

    # 格式1:${string#*substr}           # # 从左向右第一个子串出现的位置
    # 格式2:${string##*substr}          # ##从左向右最后一个子串出现的位置

echo "${teststr#*11}"                    # 打印 2.11_202204111015.log

echo "${teststr##*11}"                   # 打印 015.log

主串#*子串:      删除第一个出现子串包括子串的前半段,保留主串的后半段

主串##*模式串:   删除最后一个出现子串包括子串的前半段,保留主串的后半段


主串%子串*:      删除最后一个出现的子串包括子串后边的字符,保留主串的前半段

主串%%*模式串:   删除从第一个出现子串包括子串后边的字符,保留主串的前半段

# -----------------------------------------------------------------------

5.2 判断字符串的内容

# -----------------------------------------------------------------------

${str}                  取字符串的值 等价于$str

${str-DEFAULT}          若str未被声明,那么就以DEFAULT作为其值                           等价于 ${str=DEFAULT}
${str:-DEFAULT}         若str未被声明,或str的值为空,那么就以DEFAULT作为其值            等价于 ${str:=DEFAULT}


${str+OTHER}            若str已被声明,那么其值即设置为OTHER,否则就设置NULL字符串
${str:+OTHER}           若str已被声明,且值已被设定,修改其值为OTHER,否则就设置NULL字符串

${str?ERR_MSG}          检查str是否被被声明,若未声明则打印ERR_MSG
${str:?ERR_MSG}}        检查str是否已经被声明,且被设置,否则打印ERR_MSG

${!strPrefix*}          匹配之前所有的以strPrefix开头的变量
${!strPrefix@}          匹配之前所有的以strPrefix开头的变量

echo "${str-123}"       # 对未声明的变量赋默认值                                        等价于 echo "${str=123}"
echo "${str:-456}"      # 对未声明的变量、或未初始化的变量赋默认值                      等价于 echo "${str:=456}"

var1=""
var2="000"
echo "${var1+333}"      # 对已声明但未初始化的变量 设置特定值
echo "${var2:+333}"     # 对已声明且已初始化的变量 修改为特定值

txt1=ab
txt2=cd
txt3=
echo ${!txt*}           # 获取符合以txt开头子串的所有变量的变量名

输出结果:
123
456
333
333
txt1 txt2 txt3
# -----------------------------------------------------------------------

5.3 字符串的删除、替换

# -----------------------------------------------------------------------
${str:起始位置}                 从起始位置截取到最后
${str:起始位置:长度}            从起始位置截取多长

${str#子串}                     从str的【开头】删除最短的子串匹配
${str##子串}                    从str的【开头】删除最长的子串匹配        ${str##*/} 获取文件名

${str%子串}                     从str的【结尾】删除最短的子串匹配        ${str%/*}  获取目录
${str%%子串}                    从str的【结尾】删除最长的子串匹配

${str/子串/替换的值}            使用替换的值来 代替 第一个子串 (精准匹配)
${str//子串/替换的值}           使用替换的值来 代替 所有子串   (精准匹配 替换所有匹配子串)

${str/#子串/替换的值}           如果str中前缀匹配到子串 就用替换的值(只能替换对应的开头)
${str/%子串/替换的值}           如果str中后缀匹配到子串 就用替换的值(只能替换对应的后缀)

原串:str=abc12342341

echo ${str#a*3}                 # 从头部开始 去除子串最短匹配格式 a*3  输出结果:42341

echo ${str#c*3}                 # 这样是什么都不会匹配到的  输出结果:abc12342341 

echo ${str##a*3}                # 41 从头部开始 去掉最长匹配的字符

echo ${str%3*1}                 # abc12342 从尾部开始,去掉最短匹配

echo ${str%%3*1}                # abc12    从尾部开始,去掉最长匹配

echo ${str/23/bb}               # abc1bb42341 从头开始 替换一次

echo ${str//23/bb}              # abc1bb4bb41 从头开始 替换所有

echo ${str/#abc/bb}             # bb12342341  #代表以什么开头来匹配

echo ${str/%abc/bb}             # abc123423bb %代表以什么结尾来匹配

特例:
test='c:/windows/boot.init'
echo ${test//\//\\}             # 主串//查找/替换值  //表示替换所有 
                                # 查找值 需转义 "\/" (查找内容/)
                                # 替换 /
                                # 替换值 需转义 “\\” (替换内容\)
# -----------------------------------------------------------------------

5.4 字符串的长度、Index(默认起始index=1)、字符串的匹配

# -----------------------------------------------------------------------
${#str}                         求字符串的长度           等价于 expr length $str

str="123.hello.json"
expr index $str '123'           # 起始index = 1       结果:1
expr index $str "h"                                   结果:5
expr index $str "."             # 第一个.出现的位置   结果:4

expr substr $str 3 3            # 截取字符串 从index = 3 截取三个字符

# 自定义显示匹配子串(两种格式)
(1)expr match $str '模式子串' # expr match $str '\([a-c]*[0-9]*\)'       \(对圆括号转义 匹配内容

(2)expr $str : '模式子串'     # expr $str '\([a-c]*[0-9]\)'
# -----------------------------------------------------------------------

5.5 字符串的比较、连接

(1) 比较
[[ "a.txt" == a* ]]                # 逻辑真 pattern 匹配
[[ "a.txt" =~ .*\.txt ]]           # 逻辑真 Regex 匹配
[[ "abc" == "abc" ]]               # 逻辑真 string 比较
[[ "11" < "2" ]]                   # 逻辑真 按ASCII码值比较

(2) 连接
s1='hello'
s2='world'
echo ${s1}${s2}                    # helloworld  中间也可以加空格 echo ${s1} ${s2} 输出结果:hello world
⚠️ **GitHub.com Fallback** ⚠️