linux 学习4-文本查询替换 正则 find sed grep awk

315 阅读6分钟

1.正则表达式与文本搜索

元字符

. # 匹配除换行符外的任意单个字符
* # 匹配任意一个跟在它前面的字符
[] # 匹配方括号中的字符类中的任意一个
^ # 匹配开头
$ # 匹配结尾
\ # 转义后面的特殊字符

# 扩展元字符
+ # 匹配前面的正则表达式至少出现一次
? # 匹配前面的正则表达式出现零次或一次
| # 匹配它前面或后面的正则表达式

grep 文本内容的过滤(查找)

grep 选项 文本文件1 [ … 文本文件n ]

  • -i #忽略大小写
  • -r #递归读取每一个目录下的所有文件
grep 'password' /root/anaconda-ks.cfg # ''可以省略
grep pass.... /root/anaconda-ks.cfg # .是匹配任意单个字符
grep pass....$ /root/anaconda-ks.cfg # 只匹配 pass+4位任意
grep pass.*$ /root/anaconda-ks.cfg # 匹配pass+任意


grep ^# /root/anaconda-ks.cfg # 查询注释开头
grep . /root/anaconda-ks.cfg # 查询单个字符
grep \. /root/anaconda-ks.cfg # 查询.的内容

find 文件的查找命令

find 路径 查找条件 [ 补充条件 ]

cd /etc
find password
ls passwo*

find /etc -name passwd # 递归查询所有文件名为 passwd的内容,查出多条

find /etc/ -regex .*wd # 正则匹配 
find /etc -regex .etc.*wd$ # 和上面结果一样 指定文件路径必须包含 /etc/ 
find /etc/ -type f -regex .*wd   # 和上面结果一样 -type f 指定为文件类型  -type d 查询文件夹
# /etc/passwd
# /etc/security/opasswd
# /etc/pam.d/passwd

touch '123' > abc
stat abc # 用于查看单个文件详细信息 创建,修改 日期
LANG=C stat abc # LANG=C 解决乱码

touch /tmp/{1..9}.txt # 创建
find /tmp -regex  /tmp.*.txt  # 查询
cd /tmp ; find *.txt  -exec rm -v {} \; # 查询后 多个文件立即执行删除

# cut 分割命令
-d " "  # 分割的单位
-f 1  #分割第几段
grep passalgo /root/anaconda-ks.cfg # 
# auth --enableshadow --passalgo=sha512
grep passalgo /root/anaconda-ks.cfg | cut -d " " -f 1 # cut 以空字符分隔 取第一个
# auth 
grep passalgo /root/anaconda-ks.cfg | cut -d " " -f 2 # cut 以空字符分隔 取第二个
# --enableshadow
grep passalgo /root/anaconda-ks.cfg | cut -d " " -f 3 # cut 以空字符分隔 取第三个
# --passalgo=sha512

# uniq -c 去重复 
cut -d ":" -f 7 /etc/passwd | uniq -c  # cut 条件 + 文件夹 | 管道符过滤

# sort 做排序
cut -d ":" -f 7 /etc/passwd | sort |  uniq -c
# | sort -r 做倒叙
cut -d ":" -f 7 /etc/passwd | sort |  uniq -c | sort -r

# 日期处理
# 创建两个月前文件
touch -d "01/03/2023" access.log.2023010{1..5}
# ./access.log.20230101.log
# ./access.log.20230102.log
# 批量删除 2月前的数据 
# *20[1-9][3-9]* 指的是 2013 - 2099 的年份 
# xargs 把列表数据 转成 空格分隔
find . -mtime +58 -name "*20[1-9][3-9]*" | xargs rm -f

# 获取今天日期
YESTERDAY=$(date -d "today" +%Y%m%d)
# 20230103
# 获取昨天日期
YESTERDAY=$(date -d "yesterday" +%Y%m%d)
# 20230102

2.行编辑器 sed 和 awk 介绍

  • 交互式与非交互式
  • 文件操作模式与行操作模式
# sed 文本内容做替换 
sed '/user1/s/user1/u1/' /etc/passwd
# awk 一般用于对文本内容进行统计、按需要的格式进行输出
cut 命令: cut -d : -f 1 /etc/passwd
awk 命令: awk -F: '/wd$/{print $1}' /etc/passwd

3.sed

sed 的基本工作方式是:

  1. 将文件以行为单位读取到内存(模式空间)
  2. 使用sed的每个脚本对该行进行操作
  3. 处理完成后输出该行
  4. 默认是不会覆盖源文件

sed 的替换命令

sed 's/old/new/' filename
sed -e 's/old/new/' -e 's/old/new/' filename  …
sed -i 's/old/new/' 's/old/new/' filename  … # -i 写入源文件
# 例子
echo 'a b c' > file
sed 's/a/x/' file # 输出  x b c 源文件file不变
sed  -e 's/a/x/' -e 's/x/y/' file # 输出  y b c 源文件file不变 
sed 's/a/x/;s/x/y/' file # 输出  y b c 源文件file不变
sed -i 's/a/x/;s/x/y/' file # 输出  y b c 同时写入源文件
sed 's/a/x/;s/x/y/' file > file2 # 输出  y b c 同时写入file2

sed 的正则命令

带正则表达式的替换命令 s:
sed 's/正则表达式/new/' filename
sed -r 's/扩展正则表达式/new/' filename
# 例子
# 替换bin改为空
head -5 /etc/passwd | sed 's/s*bin//' 
# 替换开头为root改为空
grep root /etc/passwd |  sed 's/^root//'
# 例子2
vi test2
a
aaa
abb
aba
bba
cccaddd

sed 's/ab./!/' test2
# a
# aaa
# !
# !
# bba
# cccaddd

sed 's/ab*/!/' test2 #  匹配任意一个跟在*前面的字符 ,即b可以有多个,或者没有。 
# !
# !aa
# !
# !a
# bb!
# ccc!ddd

sed 's/ab?/!/' test2 # 替换无效 ?属于扩展正则,需要加 -r
sed -r 's/ab?/!/' test2 # 打开扩展正则  匹配前面的正则表达式出现零次或一次
# !
# !aa
# !b
# !a
# bb!
# ccc!ddd

sed -r 's/ab+/!/' test2 # + 匹配前面的正则表达式至少出现一次
# a
# aaa
# !
# !a
# bba
# cccaddd


sed -r 's/ab|/!/' test2 # 打开扩展正则 | # 匹配它前面或后面的正则表达式
# !a
# !aaa
# !b
# !a
# !bba
# !cccaddd

sed -r 's/a|b/!/' test2 # 打开扩展正则 | # 匹配它前面或后面的正则表达式
# !
# !aa
# !bb
# !ba
# !ba
# ccc!ddd
 
sed -r  's/(aa)|(bb)/!/' test2 # 分组 即正则匹配多次规则 先匹配 aa 再匹配bb
# a
# !a
# a!
# aba
# !a
# cccaddd

#例子3
echo 'axyzb' > test3
sed -r 's/(a.*b)/\1 \1/' test3 # 输出 axyzb axyzb
sed -r 's/(a.*b)/\1 aaaaa/' test3 # 输出 axyzb aaaa

# sed 的替换命令加强版 
# 全局替换
s/old/new/g
g #为全局替换,用于替换所有出现的次数
/ #如果和正则匹配的内容冲突可以使用其他符号,如:
s@old@new@g

# 例子
vi file2 
aaaa 222  
bbbb 222
cccc 333
sed '/bbbb/s/222/999/g' file2
# aaaa 222
# bbbb 999 # 修改bbb 的这一行被修改
# cccc 333


head -5 /etc/passwd | sed 's/root/xxxx/g' # 把所有root 替换为  /xxxx/
head -5 /etc/passwd | sed 's/root/xxxx/2' # 只替换第二个满足的root 
head -5 /etc/passwd | sed 's/root/xxxx/p' # 替换每一行满足的第一个 root  
head -5 /etc/passwd | sed -n 's/root/xxxx/p' # -n 禁止默认输出
head -5 /etc/passwd | sed -n 's/root/xxxx/w  /tmp/test.txt'  # 查询同时输出文件


# 标志位
s/old/new/标志位
数字,第几次出现才进行替换
g #每次出现都进行替换
p #打印模式空间的内容
sed -n 'script' filename #阻止默认输出
w file #将模式空间的内容写入到文件

# 寻址
默认对每行进行操作,增加寻址后对匹配的行进行操作

- /正则表达式/s/old/new/g
- 行号s/old/new/g
- 行号可以是具体的行,也可以是最后一行 $ 符号
- 可以使用两个寻址符号,也可以混合使用行号和正则地址
# 例子
head -6 /etc/passwd | sed 's/adm/xxxxx/'
head -6 /etc/passwd | sed '1,5s/adm/xxxxx/' # 从第1到第5行 
head -6 /etc/passwd | sed '1,$s/adm/xxxxx/' # 从第1到最后一行

head -6 /etc/passwd | sed '/root/s/adm/xxxxx/' # 从包含/root 开始的行
head -6 /etc/passwd | sed '2s/nologin/xxxxx/' # 从第二行开始
head -6 /etc/passwd | sed '/^bin/s/nologin/xxxxx/' # 匹配 bin开头的
head -6 /etc/passwd | sed '/^bin/,$s/nologin/xxxxx/' # 替换所有满足
head -6 /etc/passwd | sed '/^bin/,$s/nologin/xxxxx/g' # 替换所有满足


# 分组
寻址可以匹配多条命令
/regular/ { s/old/new/ ; s/old/new/ }

# 脚本文件
可以将选项保存为文件,使用-f 加载脚本文件
sed -f sedscript filename

sed 的其他命令

# 通用例子
vi test
aa
bbaa 
cc 

# 删除命令 
sed '/条件/d'

sed '/aa/d' test  #结果 输出 cc
sed '/aa/d;s/ccc/xxx/' tt  #结果 输出 xxx

# i 插入,在匹配行前
sed '/aa/i jason' test  

# a 追加,在匹配行后
sed '/aa/i jason' test  

# c 整行替换
sed '/aa/c jason' test  

#  r 读文件命令
sed '/aa/r' test  

# w 写文件命令 
sed '/aa/w /tmp/newfile' test  

# p 打印命令
sed '/aa/p' test  

# n 下一行命令 
 
# = 打印行号命令 
sed '/aa/=' test  

# q 退出命令 
sed 10q   test   # 打印文件前10行内容(到第10行后退出sed脚本指令)。

seq 1 100000 > lines.txt 
wc -l lines.txt
# time 开头可以计算执行时机
time sed -n '1,10p' lines.txt  
time sed 10q lines.txt  # 10q 速度更快

sed 的多行模式

解决什么问题

  • 配置文件一般为单行出现
  • 使用 XML 或 JSON 格式的配置文件,为多行出现

# 多行匹配命令
N # 将下一行加入到模式空间
D # 删除模式空间中的第一个字符到第一个换行符
P # 打印模式空间中的第一个字符到第一个换行符

# 例子
vi test
aaa
bbb

sed 'N' test
sed 'N;s/aaa\nbbb/xxxxx/' test # 把两行合并成一行

cat > b.txt << EOF
hell
o bash hel
lo bash
EOF

sed 'N;s/\n//;s/hello bash/hello sed\n/;P;D' b.txt # 把三行合并成一行,并且替换内容

保持空间

  • 保持空间也是多行的一种操作方式
  • 将内容暂存在保持空间,便于做多行处理

保持空间命令

h 和 H 将模式空间内容存放到保持空间 (小写h追加,大写H覆盖) g 和 G 将保持空间内容取出到模式空间 x 交换模式空间和保持空间内容 image.png

例子

 head -6 /etc/passwd |  cat -n
 head -6 /etc/passwd |  cat -n | tac # cat 倒叙

 cat -n  /etc/passwd  |  head -6 | sed -n '1h;G;x;$p'
 cat -n  /etc/passwd  |  head -6 | sed -n '1h;1!G;x;$p'
 cat -n  /etc/passwd  |  head -6 | sed -n '1h;1!G;$!x;$p'

 cat -n  /etc/passwd  |  head -6 | sed -n 'G;h;$p'
 cat -n  /etc/passwd  |  head -6 | sed -n '1!G;h;$p'
 cat -n  /etc/passwd  |  head -6 | sed '1!G;h;$!d'

4.awk

awk 和 sed 的区别

  • awk 更像是脚本语言
  • awk 用于"比较规范"的文本处理,用于统计数量并输出指定字段
  • 使用 sed 将不规范的文本,处理为"比较规范"的文本

awk 脚本的流程控制

  • 输入数据前例程 BEGIN{ }
  • 主输入循环{ }
  • 所有文件读取完成例程 END{ }

awk 的字段引用和分离

记录和字段

  • 每行称作 awk 的记录
  • 使用空格、制表符分隔开的单词称作字段
  • 可以自己指定分隔的字段

字段的引用

awk 中使用 $1 $2$n 表示每一个字段
awk '{ print $1, $2, $3}' filename
awk 可以使用 -F 选项改变字段分隔符
awk -F ',' '{ print $1, $2, $3}' filename
# 分隔符可以使用正则表达式

例子

awk '/^menu/{ print $0 }'  /boot/grub2/grub.cfg
awk -F "'" '/^menu/{ print $2 }'  /boot/grub2/grub.cfg
awk -F "'" '/^menu/{ print x++ "test....", $2 }'  /boot/grub2/grub.cfg

awk 的表达式


# 赋值操作符
= 是最常用的赋值操作符
var1 = "name"
var2 = "hello" "world"
var3 = $1

# 其他赋值操作符
++ - - += -= *= /= %= ^=
# 算数操作符 
+ - * / % ^
# 系统变量
FS # 和 OFS 字段分隔符,OFS 表示输出的字段分隔符
RS # 记录分隔符
NR # 和 FNR 行数
NF # 字段数量,最后一个字段内容可以用 $NF 取出
# 关系操作符
< > <= >= == != ~ !~
#布尔操作符 
&& || !

例子

 head -5 /etc/passwd |  awk 'BEGIN{FS=":"}{print $1}' # 取出前5条冒号 前面的 值
 head -5 /etc/passwd |  awk 'BEGIN{FS=":"}{print $1 , $2 }' # 取出前5条冒号 前面的 值 和冒号后面的值,注意打印使用,逗号分隔
 head -5 /etc/passwd |  awk 'BEGIN{FS=":";OFS="-"}{print $1 , $2 }' # ;OFS="-" 添加显示分割符号
 head -5 /etc/passwd |  awk 'BEGIN{RS=":";}{print $1}' 
 head -5 /etc/passwd |  awk '{print NR,$0} ' 
 head -5 /etc/passwd |  awk '{print FNR,$0} ' 

 awk '{print FNR,$0} '  /etc/hosts /etc/hosts # 同时处理多个文件
 awk '{print NR,$0} '  /etc/hosts /etc/hosts # 同时处理多个文件

 head -5 /etc/passwd |  awk 'BEGIN{FS=":";}{print NF}' 
 head -5 /etc/passwd |  awk 'BEGIN{FS=":";}{print $NF}' 

条件和循环


# 通用例子
cat > file << EOF
user1 70 72 74 76 74 72
user2 80 82 84 82 80 78
user3 60 61 62 63 64 65
user4 40 89 88 87 86 85
user5 45 60 63 62 61 50
EOF


# if 条件语句使用 if 开头,根据表达式的结果来判断执行哪条语句
if (表达式)
awk语句1
[ else
awk语句2
]
# 如果有多个语句需要执行可以使用 { } 将多个语句括起来
# 例子
awk  '{if($2>=80) print $1}' file # 找到第二列大于等于80的 
awk  '{if($2>=80) print $1 , $2}' file # 找到第二列大于等于80的 
 

# while 循环
while( 表达式 )

# do 循环
do{
awk 语句1
}while( 表达式 )
循环

# for循环
for( 初始值 ; 循环判断条件 ; 累加 )
影响控制的其他语句
break
continue


# 例子
head -1 file | awk '{for(c=2;c<=NF;c++) print c}'
head -1 file | awk '{for(c=2;c<=NF;c++) print $c}'
head -1 file | awk '{for(c=2;c<=NF;c++) sum+=$c ;print sum}' # 求合计
head -1 file | awk '{for(c=2;c<=NF;c++) sum+=$c ;print sum/(NF-1)}' # 求合计
awk '{for(c=2;c<=NF;c++) sum+=$c ;print sum/(NF-1)}' file
awk '{sum=0 ; for(c=2;c<=NF;c++) sum+=$c ;print sum/(NF-1)}' file

数组

# 数组的定义
数组 :一组有某种关联的数据(变量),通过下标依次访问
数组名[ 下标 ] = 值
下标可以使用数字也可以使用字符串
# 数组的遍历
for( 变量 in 数组名)
使用 数组名 [ 变量] 的方式依次对每个数组的元素进行操作
# 删除数组
delete 数组[ 下标 ]
# 命令行参数数组
命令行参数数组
ARGC
ARGV

# 例子1
awk '{sum=0 ; for(c=2;c<=NF;c++) sum+=$c ;average[$1]=sum/(NF-1)} END {for( user in average ) print user,average[user]}' file

awk '{sum=0 ; for(c=2;c<=NF;c++) sum+=$c ;average[$1]=sum/(NF-1)} END {for( user in average ) ;sum2+=average[user];print sum2}' file

函数

# 算数函数
sin( ) cos( )
int( )
rand( ) srand( )

# 字符串函数
gsub( r, s, t )
index( s, t )
length( s )
match( s, r )
split( s, a, sep )
sub( r, s, t )
substr( s, p, n )


# 例子1
awk 'BEGIN{pi=3.14;print int(pi)}'
awk 'BEGIN{print rand()}'

awk 'BEGIN{srand();print rand()}'
awk 'BEGIN{srand();print rand()}'


# 自定义函数
function 函数名( 参数 ) {
awk语句
return awk变量 
}

完整例子

cat > avg.awk << EOF
BEGIN{
for(x=0;x<ARGC;x++)
    print ARGV[x]
print ARGC
}
EOF
awk -f avg.awk file 11 22 33 44 # 第一个和第二个参数是 awk , file
# 输出
# awk 
# file
# 11
# 22
# 33
# 44
# 6

# 例子2
cat > test.awk << EOF
{
sum = 0
for( column = 2 ; column <= NF; column++ )
	sum += $column

average[$1] = sum / ( NF - 1 )
if( average[$1] >= 80 )
	letter = "S"
else if( average[$1] >= 70 )
	letter = "A"
else if( average[$1] >= 60 )
	letter = "B"
else
	letter = "C"

print $1,average[$1],letter
letter_all[letter]++
}

END{
for( user in average )
	sum_all += average[user]
	avg_all = sum_all / NR
	print "average all:",avg_all

for( user in average )
	if( average[user] > avg_all )
		above++
	else
		below++

print "above",above
print "below",below
print "S:",letter_all["S"]
print "A:",letter_all["A"]
print "B:",letter_all["B"]
print "C:",letter_all["C"]
}
EOF
awk -f test.awk file 

参考

time.geekbang.org/course/intr…