Linux命令 —— sed和awk

1. sed命令

  • sed命令跟awk一样,是一个非常强大的处理命令
  • 目前,自己使用到的具体应用场景如下:
    • 通过sed删除文件中匹配的行
    • 通过sed命令修改文件中匹配的行
    • 通过sed命令修改前一命令的执行结果等
  • 总的来说,就是通过sed命令实现字符串的修改

1.1 字符串替换

  • 直接修改字符串,例如将10.193.183.131改成10-193-183-131
    • s表示替换指定字符,
    • 末尾的g表示全面替换,类似Java String类的replaceAll,否则默认替换第一个匹配的字符
    • 整体写法:"s/source-string/target-string/g",表示使用 target-string 将匹配行中所有source-string
      hostname -i | sed "s/\./-/g"
      

1.2 删除匹配行

  • 删除匹配行,末尾使用d选项

    # 删除包含sunrise的行
    echo "hello, lucy
    hello, sunrise" | sed "/sunrise/d" 
    # 执行结果
    hello, lucy
    

1.3 基于文件的修改

  • 通过-i选项基于文件进行修改,如果怕直接修改出问题,可以 -iSUFFIX创建备份文件,SUFFIX是自定义的后缀名

    # 文件内容
    hello, world
    sunrise lucy
    10.193.24.145
    
    # 以备份文件的形式进行修改
    sed -i_bak_20220120 "s/\./-/g" test 
    # 将创建test_bak_20220120备份文件,test内容修改如下
    hello, world
    sunrise lucy
    10-193-24-145
    
  • 向文件中插入多行,通过\n实现

    sed -i "s/hello, world/hello, jack\nhello, grace/g" test  
    # 修改后的内容
    hello, jack
    hello, grace
    sunrise lucy
    10-193-24-145
    

1.4 正则匹配

  • -r正则匹配,如删除以hello开头的行

    sed -ri "/^hello.*/d" test  
    # 经过实践,不加r也是可以的
    sed -i "/^hello.*/d" test  
    # 修改后的内容
    sunrise lucy
    10-193-24-145
    
  • 例如,正则匹配,删除多余的空格

    echo "   hello|   hadoop  | test" | awk -F '|' '{print $2}' | sed -r 's/( )+//g'
    
  • 其余可以参考:sed命令

1.5 如何插入变量?

  • 有时,我们希望将匹配到的内容,更新为一个变量中的内容
  • 可以直接在target-string部分,使用变量
    ip=`hostname -i |sed 's/\./-/g'`
    node_id="node.id=coordinator-"${ip}
    sed -ri  "s/^node.id.*/${node_id}/g" $HOME/node.properties
    

1.6 其他小技巧汇总

  • 删除指定字符,如最后1个、开始的3个,使用.表示匹配一个任意字符,;实现多个操作指令的拼接。更多操作,见博客:使用 sed 命令替换/删除 文本字符的 20 个例子

    # 删除最后一个字符
    echo "250G" | sed -r "s/.{1}$//"
    # 删除前3个字符
    echo "123hello" | sed -r "s/...//"
    # 删除最后一个x
    echo "hellox" | sed -r "s/x$//"
    # 同时删除前3个和最后一个字符
    echo "123hellox" | sed -r "s/...//;s/x$//"
    

2. awk命令的使用

  • awk的由来:awk命令的作者为Aho、Weinberger和Kernighan,通过作者名字首字母组合得到了awk
  • 在我看来,awk命令就是按列处理文本数据的一个强大而实用的命令
  • 例如,可以按照指定的分隔符对文本记录进行分割,可以打印分割出来的任意列,可以求某列的最大值、最小值、和、平均值等
  • 关于awk命令的全面讲解,可以参考如下博客:

2.1 awk命令概述

2.1.1 Record & Field

  • awk中,使用RS(记录分割符)分割出的一条数据,叫做一条记录,Record。

  • awk命令的重点,就是使用FS(字段分割符)对一条Record进行分割,得到若干字段,Field

  • 默认的RS是换行符\n,默认的FS空格(连续的若干空格)

  • test文件记录的是员工的工资条信息,其内容如下:

    100  Thomas  Manager    Sales       $5000
    200  Jason   Developer  Technology  $5500
    300  Sanjay  Sysadmin   Technology  $7000
    400  Nisha   Manager    Marketing   $9500
    500  Randy   DBA        Technology  $6000
    
  • 通过awk命令的默认分割符进行分割后,100 Thomas Manager Sales $5000就是一条Record,100ThomasManager等就是Field

2.1.2 awk命令语法

  • awk命令的完整结构如下

    awk 'BEGIN{action1; action2; ...;}  
    	{action1; action2; ...;} 
    	END{action1; action2; ...;}' 
    
  • 其中,BEGIN用于定义对每条记录进行处理前的一些初始化工作,例如,定义变量、设置分割符等

  • END用于定义处理完所有记录后的一些收尾工作,例如,打印处理的记录总数,最大值、最小值等

  • BEGINEND动作块中间的动作块,则表示对每条记录进行的操作(自己喜欢称其为record action part

  • 一个完整的awk命令示例如下:

    # 整体结构为
    begin action part [可选]
    record action part [必须]
    end action part [可选]
    
  • 按照awk命令的完整结构,对test文件进行处理,实现工资条的打印并统计员工数

    # FS:指令
    awk 'BEGIN{FS="$"}  
    	{print $NF} 
    	END{print "员工数:", NR}' test
    
  • 执行结果如下:

注意事项

  • BEGINEND后面必须跟着使用{action1; action2; ...}表示的动作块(action part),不允许换行;如果换行,命令执行会报错!!

  • 例如,下面的命令将会执行报错,提示BEGIN blocks must have an action part

    awk 'BEGIN 
    	{FS="$";} 
    	{print $1, $2}' test
    

2.2 文本分割与打印

2.2.1 打印所有列(整条记录)

  • 使用awk进行分割并打印所有列,等价于cat命令。其中,$0表示整条记录

    # 不指定具体的列,默认print所有列 
    awk '{print;}' test      
    # 或者,省略最后一个action的;
    awk '{print}' test      
    # 或者,显式指定打印整条记录
    awk '{print $0}' test      
    
  • 执行结果:

2.2.2 打印指定列

  • 使用awk进行分割并打印指定列

  • 其中,$N表示第N列,$0表示整条记录;NF是记录中的字段(列)数,$NF则表示最后一列的值

    # 打印第二列和最后一列
    awk '{print $2, $5}' test
    # 或者
    awk '{print $2, $NF}' test
    
  • 执行结果:

  • 除了NF,还有一个内置变量NR,用于表示处理过的记录条数,是一个不断变化的值,相当于每条记录的编号。

  • 当处理完最后一条记录时,NR对应的就是记录的总条数

    # 为每条记录加上行号
    awk '{print NR, $0} END{print "共有", NR, "条薪资记录"}' test
    

2.2.3 -F或FS,指定Field分割符

  • awk默认分割符为空格,可以通过-F指定分割符

    # 以$作为分隔符,产生2列数据
    awk -F '$' '{print $1, $2}' test
    
    
  • 执行结果

  • -F对应awk的内建变量FS,上面的命令可以书写如下

    awk 'BEGIN{FS="$";} {print $1, $2}' test
    

2.2.4 OFS,指定打印时的Field分隔符

  • 通过以上命令的执行,我们不难发现:通过print打印每列时,依然使用空格作为分隔符

  • 有时,我们需要使用其他的分隔符。这时,可以使用awk的内建变量OFS指定打印时的分隔符

  • |作为列分割符

    awk 'BEGIN{OFS=" | "} {print $1, $2, $NF}' test 
    
  • 打印效果如下:

2.2.5 RS,指定Record分割符

  • tmp_file文件内容如下,---------------是Record分割符,如果再使用默认的\n做分割则不再适用

    100  Thomas  Manager    Sales       $5000
    ---------------
    200  Jason   Developer  Technology  $5500
    
  • 这时候,可以使用RS指定分隔符,对文本进行分割

    awk 'BEGIN{RS="---------------\\n"} {print $0}' tmp_file
    

2.2.6 printf格式化打印

  • 笔者有一个需求:通过print打印的各字段之间无任何分隔符(看起来就是分隔符为空字符串''

  • 使用如下命令成功打印:

    awk 'BEGIN{OFS=""} {print $2,$NF}' test
    
  • 需求进阶:员工姓名长度不一致,希望左对齐,占据相同宽度,然后再打印员工薪水,类似编程语言中的printf("%-20s%s", $1, $NF)

  • 通过查阅资料,发现awk也提供了printf操作,用于格式化打印

  • 上述需求,对应的awk命令:

    awk '{printf "%-8s%s\n", $2, $NF}' test # 注意需要手动换行
    
  • 执行结果如下:

  • 关于printf更多的信息,可以查阅下面的资料

2.3 求和、平均值、最大值、最小值

  • 首先,需要将test文本中,薪资列的,去除避免影响计算

2.3.1 求和

  • 实战:求员工薪水的和

    # sum+=$NF,定义sum变量,并对薪资列进行累加求和
    awk -F '$' '{sum+=$NF} END {print "员工薪水总和:", sum}' test 
    
  • 执行结果:

2.3.2 求平均

  • 求平均值,print时,使用sum / NR即可

  • 实战:求员工薪水的平均值

    awk -F '$' '{sum+=$NF} END {print "员工平均工资:", sum/NR}' test 
    
  • 执行结果

2.3.3 计算max

  • 实战:计算最高薪资

    awk -F '$'  'BEGIN {max_salary=0} 
    	{if($NF > max_salary) max_salary=$NF} 
    	END { print "最高薪资:", max_salary}'  test
    # 简写如下
    awk -F '$'  '{if($NF > max_salary) max_salary=$NF} 
    	END { print "最高薪资:", max_salary}'  test
    # 等价于if语句
    awk -F '$'  '$NF > max_salary {max_salary=$NF; max_record=$0 }; 
    	END { print "最高薪资:", max_salary, "\n完整记录 --", max_record}'  test
    
  • 执行结果1:

  • 执行结果2:

  • 执行结果3:

2.3.4 计算min

  • 实战:计算最低薪资
  • 注意:
    • 最小值必须指定,求最大值时的2和3两种写法在这里不可行
    • 建议使用第三种计算方法,避免min初始值指定不正确,导致计算出错
    # not working的写法
    awk -F '$'  '$NF < min_salary {min_salary=$NF;}; END { print "最低薪资:", min_salary}'  test
    # 设置一个合理的初始值
    awk -F '$'  'BEGIN {min_salary=10000000} {if($NF < min_salary) min_salary=$NF} END { print "最低薪资:",  min_salary}'  test
    # 最智能的做法,将第一条记录的值作为初始值
    awk -F '$' '{
    	if(NR == 1) 
        	{line = $0; min = $NF;}
    	else if(NR > 1 && $NF < min)
        	{line = $0; min = $NF;}
        }
    	END {print min,"--",line}' test
    
  • 执行结果:
    在这里插入图片描述
  • 感谢stackoverflow给的计算min的灵感:awk script for finding smallest value from column

2.4 if语句

  • 具体可以参考文档:4 Awk If Statement Examples ( if, if else, if else if, :? )

  • 一般的语法如下:

    if(conditional-expression1)
    	action1;
    else
    	action2;
    
  • 其中,action可以是使用{}包裹的多个action的组合

    {
    	action1;
    	action2;
    }
    
  • 自己的示例

    cat $file | grep "Full GC" -A 2 \
    	| awk 'BEGIN{max=0;OFS="";target="2022-06-15T00:00:00"} 
    		{if($1>max) max=$1} 
    		END{if(max>0) {print "晚于",target,"的最长full gc时间: ",max,"秒"}
        		else {print "不存在晚于",target,"的full gc"}}'
    

2.5 如何处理多行

  • 自己的文件内容如下, --\n(\n是隐形的)是一条完整的record的分隔符,\n是每个field的分隔符

    2022-06-06T10:49:18.858+0800: 52336.450: [Full GC (Allocation Failure)  177G->121G(180G), 153.0179210 secs]
       [Eden: 0.0B(9216.0M)->0.0B(9216.0M) Survivors: 0.0B->0.0B Heap: 177.0G(180.0G)->121.1G(180.0G)], [Metaspace: 153392K->151568K(178176K)]
     [Times: user=216.81 sys=5.56, real=153.02 secs] 
    --
    2022-06-06T10:52:21.935+0800: 52519.526: [Full GC (Allocation Failure)  177G->95G(180G), 108.4246053 secs]
       [Eden: 0.0B(9216.0M)->0.0B(9216.0M) Survivors: 0.0B->0.0B Heap: 177.9G(180.0G)->95.1G(180.0G)], [Metaspace: 151861K->151604K(178176K)]
     [Times: user=162.21 sys=0.53, real=108.42 secs] 
    --
    2022-06-06T13:44:04.202+0800: 62821.794: [Full GC (Allocation Failure)  177G->114G(180G), 145.3675436 secs]
       [Eden: 0.0B(9216.0M)->0.0B(9216.0M) Survivors: 0.0B->0.0B Heap: 178.0G(180.0G)->114.2G(180.0G)], [Metaspace: 152892K->152677K(180224K)]
     [Times: user=206.21 sys=3.66, real=145.36 secs] 
    --
    2022-06-06T13:46:59.241+0800: 62996.833: [Full GC (Allocation Failure)  178G->70G(180G), 88.3189255 secs]
       [Eden: 0.0B(9216.0M)->0.0B(9216.0M) Survivors: 0.0B->0.0B Heap: 178.3G(180.0G)->70.7G(180.0G)], [Metaspace: 152700K->152663K(180224K)]
     [Times: user=129.14 sys=0.74, real=88.32 secs] 
    --
    2022-06-06T14:45:15.222+0800: 66492.813: [Full GC (Allocation Failure)  179G->22G(180G), 43.2909254 secs]
       [Eden: 0.0B(9216.0M)->0.0B(9216.0M) Survivors: 0.0B->0.0B Heap: 179.1G(180.0G)->22.1G(180.0G)], [Metaspace: 154384K->152752K(180224K)]
     [Times: user=55.39 sys=0.69, real=43.29 secs] 
    
  • 我有个需求,需要获取每次full gc的时间,也就是每条record最后一个field中的real=43.29 secs

  • 而awk默认的record分隔符为\n、field分隔符为空格,这时就需要自定义分隔符了

    # RS用于指定record的分隔符,FS用于指定field的分隔符
    cat test | awk 'BEGIN{RS="--\\n";FS="\\n"} { print $3}' \
    | awk -F "real=" '{print $2}' | awk '{print $1}'
    
  • 最终的执行结果如下:
    在这里插入图片描述

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值