Linux脚本中的字符处理与awk编程

【1】字符提取与输出格式化

① cut列提取命令

语法格式

cut [选项] 文件名

选项:

  • -f 列号: 提取第几列
  • -d 分隔符: 按照指定分隔符分割列
  • -c 字符范围: 不依赖分隔符来区分列,而是通过字符范围(行首为0)来进行字段提取。“n-”表示从第n个字符到行尾;“n-m”从第n个字符到第m个字符;“-m”表示从第1个字符到第m个字符。

cut命令的默认分隔符是制表符,也就是“tab”键。

测试文本如下(用Tab键分割):
在这里插入图片描述
测试实例如下:

#提取第二列内容
[root@bogon shell]# cut -f 2 student.txt
Name
Liming
Sc
Tg

#提取2 3列内容
[root@bogon shell]# cut -f 2,3 student.txt
Name    gender
Liming  M
Sc      M
Tg      M

cut可以按照字符进行提取,需要注意“8-”代表的是提取所有行的第8个字符开始到行尾,而“10-20”代表提取所有行的第十个字符到第二十个字符,而“-8”代表提取所有行从行首到第八个字符:


[root@bogon shell]# cut -c 8- student.txt
        gender  Mark
g       M       86
90
83

“:”作为分隔符,提取/etc/passwd文件的第一列和第三列

[root@bogon shell]# cut -d ":" -f 1,3 /etc/passwd
root:0
bin:1
daemon:2
adm:3
lp:4
sync:5
shutdown:6
halt:7
mail:8
operator:11
games:12
ftp:14
nobody:99
systemd-network:192
//...

② printf格式化输出

语法格式:

printf  '输出类型输出格式' 输出内容

输出类型:

  • %ns: 输出字符串。n是数字指代输出几个字符(n可为0)
  • %ni: 输出整数。n是数字指代输出几个数字(n可为0)
  • %m.nf: 输出浮点数。m和n是数字,指代输出的整数位数和小数位数。如%8.2f代表共输出8位数,其中2位是小数,6位是整数。

输出格式:

  • \a: 输出警告声音
  • \b: 输出退格键,也就是Backspace键
  • \f: 清除屏幕
  • \n: 换行
  • \r: 回车,也就是Enter键
  • \t: 水平输出退格键,也就是Tab键
  • \v: 垂直输出退格键,也就是Tab键

修改student.txt如下:

在这里插入图片描述
测试实例如下:

[root@bogon shell]# printf '%s' $(cat student.txt)
IDNamePHPLinuxMySQLAverage1Liming82958687.662Sc74968785.663Tg99839391.66[root@bogon shell]#

可以看到文本内容直接平铺输出没有任何格式。这就是printf命令,如果不指定输出格式,则会把所有输出内容连在一起输出。

其实文本的输出本身就是这样的,cat等文本输出命令之所以可以按照格式漂亮的输出,那是因为cat命令已经设定了输出格式。

那么为了用printf输出合理的格式,应该这样做(注意在printf命令的单引号中,只能识别格式输出符号,而手工输入的空格是无效的):

[root@bogon shell]# printf '%s\t %s\t %s\t %s\t %s\t %s\t \n' $(cat student.txt)
ID       Name    PHP     Linux   MySQL   Average
1        Liming  82      95      86      87.66
2        Sc      74      96      87      85.66
3        Tg      99      83      93      91.66

如果不想把成绩当成字符串输出,而是按照整型和浮点型输出,则要这样:

#grep  -v Name) 表示不包含Name那一行,即反向查找
[root@bogon shell]# printf '%i\t %s\t %i\t %i\t %i\t %8.2f\t \n' $(cat student.txt | grep  -v Name)
1        Liming  82      95      86         87.66
2        Sc      74      96      87         85.66
3        Tg      99      83      93         91.66

awk中print与printf使用

如下实例,awk+print打印第二行数据:

[root@localhost shell]# cat student.txt
ID      Name    PHP     Linux   MySQL   Average
1       Liming  82      95      86      87.66
2       Sc      74      96      87      85.66
3       Tg      99      83      93      91.66
[root@localhost shell]# cat student.txt | awk 'NR==2{print}'
1       Liming  82      95      86      87.66

awk+printf 打印第二行数据:

[root@localhost shell]# awk 'NR==2{printf $0 "\n"}' student.txt
1       Liming  82      95      86      87.66

【2】awk编程

awk是一种编程语言,用于在linux/unix下对文本和数据进行处理。数据可以来自标准输入(stdin)、一个或多个文件,或其它命令的输出。它支持用户自定义函数和动态正则表达式等先进功能,是linux/unix下的一个强大编程工具。它在命令行中使用,但更多是作为脚本来使用。awk有很多内建的功能,比如数组、函数等,这是它和C语言的相同之处,灵活性是awk最大的优势。可以点击参考更多awk介绍。

常用命令选项

  • -F fs fs指定输入分隔符,fs可以是字符串或正则表达式,如-F:
  • -v var=value 赋值一个用户定义变量,将外部变量传递给awk
  • -f scripfile 从脚本文件中读取awk命令
  • -m[fr] val 对val值设置内在限制,-mf选项限制分配给val的最大块数目;-mr选项限制记录的最大数目。这两个功能是Bell实验室版awk的扩展功能,在标准awk中不适用。

① awk的基本语法

语法格式如下:

awk  '条件1{动作1} 条件2{动作2}…'  文件名

#完整格式如下
awk 'BEGIN{ print "start" } pattern{ commands } END{ print "end" }' file

条件(Pattern):
一般使用关系表达式作为条件,这些关系表达式非常多。例如:

  • x > 10 判断变量 x是否大于10
  • x == y 判断变量 x是否等于变量y
  • A ~ B 判断字符串A中是否包含能匹配B 表达式的子字符串
  • A !~ B 判断字符串A中是否不包含能匹配B表达式的子字符串

动作(Action):

  • 格式化输出
  • 流程控制语句

使用awk+printf进行输出:

#输出第二列和第六列
[root@localhost shell]# awk '{printf $2 "\t" $6 "\n"}' student.txt
Name    Average
Liming  87.66
Sc      85.66
Tg      91.66
#效果等同于如下
[root@localhost shell]# cut -f 2,6 student.txt
Name    Average
Liming  87.66
Sc      85.66
Tg      91.66

输出df -h的第一列和第三列:

[root@localhost shell]# df -h |awk '{printf $1 "\t" $3 "\n"}'
Filesystem      Used
devtmpfs        0
tmpfs   0
tmpfs   8.4M
tmpfs   0
/dev/mapper/centos-root 5.4G
/dev/sda1       171M
tmpfs   12K
tmpfs   0

② awk的条件

条件的类型 条 件 说 明
awk保留字 BEGIN 在awk程序一开始时,尚未读取任何数据之前执行。BEGIN后的动作只在程序开始时执行一次
awk保留字 END 在awk程序处理完所有数据,即将结束时执行。END后的动作只在程序结束时执行一次
关系运算符 > 大于
< 小于
>= 大于等于
<= 小于等于
== 等于。用于判断两个值是否相等,如果是给变量赋值,请使用“=”号
!= 不等于
A~B 判断字符串A中是否包含能匹配B 表达式的子字符串
A!~B 判断字符串A中是否不包含能匹配B表达式的子字符串
正则表达式 /正则/ “//”中可以写入字符,也可以支持正则表达式

BEGIN

BEGIN是awk的保留字,是一种特殊的条件类型。BEGIN的执行时机是“在awk程序一开始时,尚未读取任何数据之前执行”。一旦BEGIN后的动作执行一次,当awk开始从文件中读入数据,BEGIN的条件就不再成立,所以BEGIN定义的动作只能被执行一次。

如下所示,在程序开始时打印“这是一张成绩单”,然后输出student.txt中的第2列和第6列:

[root@localhost shell]# awk 'BEGIN{printf "This is a transcript \n"} {printf $2 "\t" $6 "\n"}' student.txt
This is a transcript
Name    Average
Liming  87.66
Sc      85.66
Tg      91.66

END

END也是awk保留字,不过刚好和BEGIN相反。END是在awk程序处理完所有数据,即将结束时执行。END后的动作只在程序结束时执行一次。

测试实例如下:

[root@localhost shell]# awk 'END{printf "This is end \n"} {printf $2 "\t" $6 "\n"}' student.txt
Name    Average
Liming  87.66
Sc      85.66
Tg      91.66
This is end

关系运算符

如下输出平均分大于等于97的学生姓名:

[root@localhost shell]# cat student.txt | grep -v Name
1       Liming  82      95      86      87.66
2       Sc      74      96      87      85.66
3       Tg      99      83      93      91.66
[root@localhost shell]# cat student.txt | grep -v Name|awk '$6>=87{printf $2 "\n"}'
Liming
Tg

加入了条件之后,只有条件成立动作才会执行,如果条件不满足,则动作则不运行。通过这个实验可以发现,虽然awk是列提取命令,但是也要按行来读入的。这个命令的执行过程是这样的:

  • 1) 如果有BEGIN条件,则先执行BEGIN定义的动作
  • 2) 如果没有BEGIN条件,则读入第一行,把第一行的数据依次赋予$0、$1、$2等变量。其中$0代表此行的整体数据,$1代表第一字段,$2代表第二字段。
  • 3) 依据条件类型判断动作是否执行。如果条件符合,则执行动作,否则读入下一行数据。如果没有条件,则每行都执行动作。
  • 4) 读入下一行数据,重复执行以上步骤。

如下查询Sc用户的平均分:

[root@localhost shell]# awk '$2~/Sc/{printf $6 "\n"}' student.txt
85.66

这里要注意在awk中,使用“//”包含的字符串,awk命令才会查找。也就是说字符串必须用“//”包含,awk命令才能正确识别。

正则表达式

如果要想让awk识别字符串,必须使用“//”包含。当使用df命令查看分区使用情况是,如果我只想查看真正的系统分区的使用状况,而不想查看光盘和临时分区的使用状况,则可以

[root@localhost shell]# df -h |awk '/sda[0-9]/{printf $1 "\t" $5 "\n"}'
/dev/sda1       17%

#如果想获取17则可以和cut命令配合使用
[root@localhost shell]# df -h |awk '/sda[0-9]/{print $5 "\n"}'|cut -d "%" -f 1
17

③ awk内置变量

awk内置变量 作 用
$0 代表目前awk所读入的整行数据。我们已知awk是一行一行读入数据的,$0就代表当前读入行的整行数据
$n 代表目前读入行的第n个字段
NF 当前行拥有的字段(列)总数
NR 当前awk所处理的行,是总数据的第几行
FS 用户定义分隔符。awk的默认分隔符是任何空格,如果想要使用其他分隔符(如“:”),就需要FS变量定义
ARGC 命令行参数个数
ARGV 命令行参数数组
FNR 当前文件中的当前记录数(对输入文件起始为1)
OFMT 数值的输出格式(默认为%.6g)
OFS 输出字段的分隔符(默认为空格)
ORS 输出记录分隔符(默认为换行符)
RS 输入记录分隔符(默认为换行符)

读取登录用户的名称和用户标识号(用户名:口令:用户标识号:组标识号:注释性描述:主目录:登录Shell)

[root@localhost shell]# cat /etc/passwd | grep "/bin/bash" | awk '{FS=":"} {printf $1 "\t" $3 "\n"}'
root:x:0:0:root:/root:/bin/bash
jane    1000

可以看到第一行的{FS=":"}并没有起作用,第二行才开始起作用。这是因为没有使用BEGIN保留字,awk读入每行数据后会判断是否有begin,如果没有begin则根据条件执行每个动作。修改命令如下:

[root@localhost shell]# cat /etc/passwd | grep "/bin/bash" | awk 'BEGIN{FS=":"} {printf $1 "\t" $3 "\n"}'
root    0
jane    1000

在上面基础上继续输出当前行号和字段数:

[root@localhost shell]# cat /etc/passwd | grep "/bin/bash" | awk 'BEGIN{FS=":"} {printf $1 "\t" $3 "\t 行号:"NR "\t 字符数:"NF "\n"}'
root    0        行号:1         字符数:7
jane    1000     行号:2         字符数:7

如果我只想看看sshd这个伪用户的相关信息,则可以这样使用:

[root@localhost shell]# cat /etc/passwd  | awk 'BEGIN{FS=":"} $1=="sshd"{printf $1 "\t" $3 "\t 行号:"NR "\t 字符数:"NF "\n"}'               sshd    74       行号:40        字符数:7

④ awk流程控制

4.1在awk中定义变量与调用变量的值

统计PHP成绩的总分实例如下:

[root@localhost shell]# awk 'NR==2{php1=$3}
NR==3{php2=$3}
NR==4{php3=$3;totle=php1+php2+php3;printf "totle php is " totle "\n"}' student.txt
totle php is 255

在awk编程中,因为命令语句非常长,在输入格式时需要注意以下内容:

  • 多个条件{动作}可以用空格分割,也可以用回车分割。
  • 在一个动作中,如果需要执行多个命令,需要用“;”分割,或用回车分割。
  • 在awk中,变量的赋值与调用都不需要加入“$”符。
  • 条件中判断两个值是否相同,请使用“==”,以便和变量赋值进行区分。

如下输出Linux成绩大于90的:

[root@localhost shell]# awk '{if(NR>=2){if($4>=90){printf $2 "\tis a good man\n"}}}' student.txt
Liming  is a good man
Sc      is a good man

# 这里里侧{}可以去掉,如下所示
[root@localhost shell]# awk '{if(NR>=2){if($4>=90)printf $2 "\tis a good man\n"}}' student.txt
Liming  is a good man
Sc      is a good man

#还可以修改如下
[root@localhost shell]# awk 'NR>=2{if($4>=90)printf $2 "\tis a good man\n"}' student.txt
Liming  is a good man
Sc      is a good man

#在awk中if判断语句,完全可以直接利用awk自带的条件来取代
[root@localhost shell]# awk 'NR>=2{test=$4} test>=90{printf $2 "\tis a good man\n"}' student.txt
Liming  is a good man
Sc      is a good man

⑤ awk函数

awk编程也允许在编程时使用函数,awk函数的定义方法如下:

function 函数名(参数列表){
	函数体
}

如下所示打印学生成绩:

[root@localhost shell]# awk 'function test(a,b){printf a "\t" b "\n"} {test($2,$6)}' student.txt
Name    Average
Liming  87.66
Sc      85.66
Tg      91.66

⑥ awk中调用脚本

对于小的单行程序来说,将脚本作为命令行自变量传递给awk是非常简单的,而对于多行程序就比较难处理。当程序是多行的时候,使用外部脚本是很适合的。首先在外部文件中写好脚本,然后可以使用awk的-f选项,使其读入脚本并且执行。

例如,我们可以先编写一个awk脚本

[root@localhost ~]# vi pass.awk
BEGIN {FS=":"}
{ print $1 "\t" $3}

然后可以使用“-f”选项来调用这个脚本:

[root@localhost ~]# awk -f pass.awk /etc/passwd
root 0
bin 1
daemon 2
//...

【3】sed命令

sed主要是用来将数据进行选取、替换、删除、新增的命令,语法格式如下:

sed [选项] ‘[动作]’ 文件名

选项:

  • -n: 一般sed命令会把所有数据都输出到屏幕,如果加入此选择,则只会把经过sed命令处理的行输出到屏幕。
  • -e: 允许对输入数据应用多条sed命令编辑。
  • -f 脚本文件名: 从sed脚本中读入sed操作。和awk命令的-f非常类似。
  • -r: 在sed中支持扩展正则表达式。
  • -i: 用sed的修改结果直接修改读取数据的文件,而不是由屏幕输出

动作:

  • a \: 追加,在当前行后添加一行或多行。添加多行时,除最后 一行外,每行末尾需要用“\”代表数据未完结。
  • c \: 行替换,用c后面的字符串替换原数据行,替换多行时,除最后一行外,每行末尾需用“\”代表数据未完结。
  • i \: 插入,在当期行前插入一行或多行。插入多行时,除最后 一行外,每行末尾需要用“\”代表数据未完结。
  • d: 删除,删除指定的行。
  • p: 打印,输出指定的行。
  • s: 字串替换,用一个字符串替换另外一个字符串。格式为“行范围s/旧字串/新字串/g”(和vim中的替换格式类似)。

对sed命令大家要注意,sed所做的修改并不会直接改变文件的内容(如果是用管道符接收的命令的输出,这种情况连文件都没有),而是把修改结果只显示到屏幕上,除非使用“-i”选项才会直接修改文件。

操作实例

打印student.txt第二行数据:

[root@localhost shell]# sed -n "2p" student.txt
1       Liming  82      95      86      87.66

删除第二行到第四行的数据:

[root@localhost shell]# sed "2,4d" student.txt
ID      Name    PHP     Linux   MySQL   Average

#但是文件本身并没有修改
[root@localhost shell]# cat student.txt
ID      Name    PHP     Linux   MySQL   Average
1       Liming  82      95      86      87.66
2       Sc      74      96      87      85.66
3       Tg      99      83      93      91.66

在第二行后插入数据:

[root@localhost shell]# sed "2a hello" student.txt
ID      Name    PHP     Linux   MySQL   Average
1       Liming  82      95      86      87.66
hello
2       Sc      74      96      87      85.66
3       Tg      99      83      93      91.66

在第二行前插入数据:

[root@localhost shell]# sed "2i hello" student.txt
ID      Name    PHP     Linux   MySQL   Average
hello
1       Liming  82      95      86      87.66
2       Sc      74      96      87      85.66
3       Tg      99      83      93      91.66

如果是想追加或插入多行数据,除最后一行外,每行的末尾都要加入“\”代表数据未完结:

[root@localhost shell]# sed '2i hello \
> world' student.txt
ID      Name    PHP     Linux   MySQL   Average
hello
> world
1       Liming  82      95      86      87.66
2       Sc      74      96      87      85.66
3       Tg      99      83      93      91.66

加上-n只查看sed处理过的数据:

[root@localhost shell]# sed  -n '2i hello \
 world' student.txt
hello
 world

行数据替换(替换掉第二行数据):

[root@localhost shell]# cat student.txt | sed '2c No such person'
ID      Name    PHP     Linux   MySQL   Average
No such person
2       Sc      74      96      87      85.66
3       Tg      99      83      93      91.66

sed命令默认情况是不会修改文件内容的,如果确定需要让sed命令直接处理文件的内容,可以使用“-i”选项。如下所示修改文本第二行数据:

[root@localhost shell]# sed -i '2c No such person' student2.txt
[root@localhost shell]# cat student2.txt
ID      Name    PHP     Linux   MySQL   Average
No such person
2       Sc      74      96      87      85.66
3       Tg      99      83      93      91.66

字符串替换

“c”动作是进行整行替换的,如果仅仅想替换行中的部分数据,就要使用“s”动作了。s动作的格式是:sed ‘s/旧字串/新字串/g’ 文件名

操作实例如下:

#把第三行的74 改为 99
[root@localhost shell]# sed '3s/74/99/g' student.txt
ID      Name    PHP     Linux   MySQL   Average
1       Liming  82      95      86      87.66
2       Sc      99      96      87      85.66
3       Tg      99      83      93      91.66

#第四行手添加#号表示注释
[root@localhost shell]# sed '4s/^/#/g' student2.txt
ID      Name    PHP     Linux   MySQL   Average
No such person
2       Sc      74      96      87      85.66
#3      Tg      99      83      93      91.66

同时处理多行

“-e”选项可以同时执行多个sed动作,当然如果只是执行一个动作也可以使用“-e”选项,但是这时没有什么意义。还要注意,多个动作之间要用“;”号或回车分割。

[root@localhost shell]# cat student2.txt
ID      Name    PHP     Linux   MySQL   Average
1       Liming  82      95      86      87.66
2       Sc      74      96      87      85.66
3       Tg      99      83      93      91.66

[root@localhost shell]# sed -e 's/Liming//g ; s/Tg//g' student2.txt
ID      Name    PHP     Linux   MySQL   Average
1               82      95      86      87.66
2       Sc      74      96      87      85.66
3               99      83      93      91.66

#可以修改如下
[root@localhost ~]# sed -e 's/Liming//g
> s/Tg//g' student2.txt

【4】字符处理命令

① 排序命令sort

语法格式sort [选项] 文件名

选项:

  • -f: 忽略大小写
  • -b: 忽略每行前面的空白部分
  • -n: 以数值型进行排序,默认使用字符串型排序
  • -r: 反向排序
  • -u: 删除重复行。就是uniq命令
  • -t: 指定分隔符,默认是分隔符是制表符
  • -k n[,m]: 按照指定的字段范围排序。从第n字段开始,m字段结束(默认到行尾)

sort命令默认是用每行开头第一个字符来进行排序的,如对student.txt默认排序如下:

[root@localhost shell]# sort student.txt
1       Liming  82      95      86      87.66
2       Sc      74      96      87      85.66
3       Tg      99      83      93      91.66
ID      Name    PHP     Linux   MySQL   Average

反向排序:

[root@localhost shell]# sort -r student.txt
ID      Name    PHP     Linux   MySQL   Average
3       Tg      99      83      93      91.66
2       Sc      74      96      87      85.66
1       Liming  82      95      86      87.66

以第三列字段排序:

[root@localhost shell]# sort -k 3,3 student.txt
2       Sc      74      96      87      85.66
1       Liming  82      95      86      87.66
3       Tg      99      83      93      91.66
ID      Name    PHP     Linux   MySQL   Average

当然“-k”选项可以直接使用“-k 3”,代表从第三字段到行尾都排序(第一个字符先排序,如果一致,第二个字符再排序,知道行尾)。

删除重复行:

#如下首先快速复制一行
ID      Name    PHP     Linux   MySQL   Average
1       Liming  82      95      86      87.66
1       Liming  82      95      86      87.66
2       Sc      74      96      87      85.66
3       Tg      99      83      93      91.66

#使用 -u选项取消重复行
[root@localhost shell]# sort -u student2.txt
1       Liming  82      95      86      87.66
2       Sc      74      96      87      85.66
3       Tg      99      83      93      91.66
ID      Name    PHP     Linux   MySQL   Average

#但是并不会改变源文件
[root@localhost shell]# cat student2.txt
ID      Name    PHP     Linux   MySQL   Average
1       Liming  82      95      86      87.66
1       Liming  82      95      86      87.66
2       Sc      74      96      87      85.66
3       Tg      99      83      93      91.66

② uniq命令

其实是对应unique(单独、唯一),uniq命令是用来取消重复行的命令,和“sort -u”选项是一样的。命令格式如下:

uniq [选项] 文件名

选项

  • -i: 忽略大小写
  • -d:打印重复行的一行
  • -D:打印所有重复行
  • -u:打印非重复行
[root@localhost shell]# uniq -d student2.txt
1       Liming  82      95      86      87.66

[root@localhost shell]# uniq -D student2.txt
1       Liming  82      95      86      87.66
1       Liming  82      95      86      87.66

[root@localhost shell]# uniq -u student2.txt
ID      Name    PHP     Linux   MySQL   Average
2       Sc      74      96      87      85.66
3       Tg      99      83      93      91.66

③ 统计命令wc

wc命令用来计算数字。利用wc指令我们可以计算文件的Byte数、字数或是列数,若不指定文件名称,或是所给予的文件名为“-”,则wc指令会从标准输入设备读取数据。

语法:

wc(选项)(参数)

选项:

-c: 统计字节数Bytes
-l: 只统计行数
-w: 只统计单词数
-m: 只统计字符数

参数:

文件:需要统计的文件列表。

实例

#文件实例
[root@localhost shell]# cat student2.txt
ID      Name    PHP     Linux   MySQL   Average
1       Liming  82      95      86      87.66
中      Liming  82      95      86      87.66
2       Sc      74      96      87      85.66
3       Tg      99      83      93      91.66

#统计单词数
[root@localhost shell]# wc -w student2.txt
30 student2.txt

#统计字符数
[root@localhost shell]# wc -m student2.txt
120 student2.txt

#统计字节数
[root@localhost shell]# wc -c student2.txt
122 student2.txt

#统计行数
[root@localhost shell]# wc -l student2.txt
5 student2.txt

你可能感兴趣的:(Linux全面入门)