文章目录
- 一、com
- 二、syntax
- 三、notice
- 四、实例
- 1.过滤,类似于grep
- 2.去除重复行列,类似于uniq
- 行count,去除重复行并统计每组数量
- 3.计数统计,类似于wc
- 统计某单词出现次数
- 4.取前几行,≈head
- 5.切分列,≈split
- 设置输出的分隔符,并且统计分隔符的数量
- 循环所有列,并且`输出单引号'和双引号"`的文本
- 将2个指定关键字的行之间的内容输出,且不包括关键字行
- 指定输出的列
- 不想要哪列,直接将那列设为"",然后输出$0即可
- 6.将ldap的add文件修改为modify文件
- 7.将文件中的空白行提取出,一个空白行打印一句话
- 8.将文件中空行过滤,然后每3行合并为一行
- 9. 查找
- 10. 替换
- 11. 某一列进行条件判断
- 12. 从某目录下过滤出某些文件,然后将这些文件复制后改名
- 13.逆转字符串
- 14.将文本以行为单位逆序输出
- 15.打印指定行的前几行&按行逆序
一、com
https://coolshell.cn/articles/9070.html
https://taoshu.in/awk-in-20-minutes.html
1.作用
awk是个文本处理的程序,是一种独立的编程语言,有自己独特的语法,它将一个文件分为行和列,以类似二维表的方式进行处理。
它不需要声明文件流的打开和关闭,不像java和python,它自动按行遍历,去操作每一行和列。也就是说,省去了文件流和循环的代码,直接写循环体。
它提供了内置变量和函数,并且可以通过正则筛选行
2.工作模式:按行进行匹配+处理
它的工作模式就是简单的匹配、处理。awk从标准输入逐行读取内容,然后使用不同的模式(Pattern)进行匹配,如果匹配成功,则会执行对应大括号内的动作(Action)。这就是awk的执行模型。读取、匹配、执行,就这么简单,所用复杂的功能都是在这个模型之上衍生出来的。
awk逐行扫描文件,寻找匹配特定模式的行,并在这些行上进行指定的操作,然后格式化输出。
它包含2部分:
模式匹配(用于找到要处理的行) pattern
处理 {action}
awk有两个特殊的模式:BEGIN和END,在没有读取任何数据之前以及在所有数据读取完成以后执行。一般用来声明变量和设置输出格式
工作模式总结如下:
第一步执行BEGIN 语句
第二步从文件或标准输入读取一行,然后再执行pattern+action,然后下一行
第三步执行END语句
3.awk同其他文本处理程序的对比
都是文本处理程序,并列关系,可以相互替换,awk功能最多。
awk同sed相比,awk偏重于格式化展示(其实就是把源文件给变个样子然后展示出来),所以awk要一行一行的把整个文件都给读了,有报表展示的影子。所以awk处理完,如果想把变化同步到源文件,需要重新写回。
sed偏重于文本的匹配 + 处理,即:找到想要的行,进行想要的处理,比如替换一个单词、下面添加一行。虽然这些awk也能,但sed方便一点,不需要循环读,也修改也不需要回写,因为sed的使命就是修改文件
4.awk的正则
awk的正则跟grep一样,很迷,很玄。而且不同版本之间也不一样
使用awk -V可以查看版本
在git bash中,版本为GNU Awk 5.0.0, API: 2.0 (GNU MPFR 4.1.0, GNU MP 6.2.1)
在mobaxterm自带的环境中,版本为BusyBox v1.22.1 (2015-11-10 11:07:12 ) multi-call binary.
前者,行范围表达指定行后面所有时,用/xxx/,/*/ 后者得用/xxx/,/\*/
要多加个转义。。。迷!!!
二、syntax
1.basic format
awk options program file
awk [可选的命令行选项] 'BEGIN{命令 } pattern{ 命令 }pattern{ 命令 }…… END{ 命令 }' 文件名
2.options:
-F -fs 指定行中划分数据字段的字段分隔符。awk中默认的字段分隔符是任意的空白字符(例如n个空格或n个制表符tab)
,不是只有空格和制表。
由于/etc/passwd文件是使用冒号来分离字段的,因此要在awk选项中将冒号指定为字段分隔符。
$ awk -F: '{print $1}' /etc/passwd
nobody
root
daemon
_uucp
_taskgated
_networkd
_installassistant
_lp
...
行首有空白,会自动忽略
打印出第一列不受影响
FIELDWIDTHS,根据宽度分割,空格算长度
$ cat file
1234567890
$ awk -vFIELDWIDTHS="1 2 3 4 5" -vOFS="|" 'NF=NF' file
1|23|456|7890|
-f programFile 从指定的文件中读取代码数据
-v var=value 定义awk程序中的一个变量及其默认值,设置OFS的时候,必须用-v
一个var对应一个-v
awk -v reportdate=$reportdate -v pt=$pt '{print $1"\t"$2"\t"$3"\t"$4"\t"reportdate"\t"pt}' ./转办.txt
设置OFS时,可以放在option中,也可以放在BEGIN中
awk -F: 'BEGIN{OFS="-"};/tyson/{ print ("username:"$1,"protocol:"$4)}' /etc/passwd
username:tyson-protocol:1001
username:tyson1-protocol:1003
ORS是输出的行之间的分隔符,默认是一个回车\n
awk -F: 'BEGIN{ORS="\n\n"};/tyson/{ print ("username:"$1,"protocol:"$4)}' /etc/passwd
username:tyson protocol:1001
username:tyson1 protocol:1003
3.program=模式+action
‘模式 {命令}模式 {命令}模式 {命令}…’
整体用单引号包裹
可以连续多个,多个program全都包裹在’'中,相互独立,每个program都是针对所有行
命令用{}包裹
模式中可以用正则,也可以是if这种判断,当然if也可以放在命令中
命令中可以定义变量,也有循环等语法,别忘了,awk是种编程语言
模式是用来匹配行的,如果想匹配列,那要在{}中用列变量或者循环
awk的程序脚本是单个文本字符串,因为中间有空格,所以要用单引号包裹。下面的例子在命令行上指定了一个简单的Hello World例子:
$ awk ‘{print “Hello World!”}’
没指定动作,默认为打印到控制台
{1}模式
模式是为了筛选数据,自然而然,可以:
按行:行号、行正则匹配
按列:列号、列正则匹配
混合使用:逻辑运算符
正则模式、布尔模式、特殊模式、行模式、列模式。
[1]正则模式,要用//包裹
/admin/ { ... } # 匹配包含 admin 的内容
/^admin/ { ... } # 匹配 admin 开头的内容
/admin$/ { ... } # 匹配 admin 结尾的内容
/^[0-9.]+ / { ... } # 匹配数字开头的内容
/(POST|PUT|DELETE)/ # 匹配包含部分 http 请求的内容
[2]行模式,这里的正则很迷,需要死记硬背。
syntax
A条件,B条件
注意A和B条件不一定是正则
上图中第一种语法是正则模式的语法,表示被正则表达式匹配到的行,将会执行对应的动作。
上图中第二种语法是行范围模式的语法,它表示,从被正则1匹配到的行开始,到被正则2匹配到的行结束,之间的所有行都会执行对应的动作,所以,这种模式被称为行范围模式,因为它对应的是一个范围以内的所有行,但是需要注意的是,在行范围模式中,不管是正则1,还是正则2,都以第一次匹配到的行为准
就像上述示例中,即使Lee在第2行与第3行中都出现了,但是由于正则1先匹配到第2行中的lee,所以,最终打印出的内容从第2行开始,即使Kevin在第5行与第7行中都出现了,但是由于Kevin第一次出现在第5行,所以最终打印出的内容到第5行结束,也就是说,最终打印出了第2行到第5行以内的所有行。
但是,你可能会有这样的需求,你不想依靠正则表达式去匹配行的特征,你只是想单纯的打印出从X行到Y行之间的所有行。
比如,我们有一个文本文件,这个文件中一共有7行文本,你想要打印出从第3行到第6行之间的所有行,该怎么做呢?
其实,使用之前学习到的”关系运算符模式”,即可满足我们的需求,示例如下。
行模式但不包含首尾行,见下面case
行模式中取筛选后数据的行号
- 使用行模式筛选数据后,输出NR,输出的还是未筛选前的NR
- 如果想将筛选后的数据从1开始编号,可以使用i++
- 筛掉前args行
- 筛掉后args行
行模式,指定某行上面或下面所有行
指定某行下面所有行,2种方法
####### 下面所有用/*/表示,为啥不是.*
?,不明白,可能这里不是完全正则,是通配符
$ awk '/^CR/,/*/' hive_create_table1.sh
CREATE
CREATE
CREATE
CREATE TABLE
`col1` string,
`col2` string,
`col3` string
COMMENT 'comment text'
ROW
ROW
COMMENT
####### 使用action内的变量
- 筛选某行后的所有行
指定某行上面所有行,跟上面不一样,这里用*就成所有的了,奇怪。可以用NR==1。这里的第二个条件指的是第一个匹配的
$ awk 'NR==1,/CREATE/' hive_create_table1.sh
SLF4j:123
SLF4j:456
22/06/17
22/06/17
22/06/17
CREATE
[3]布尔模式|列模式
$1 >= 500 { ... } # 匹配 5xx 请求
/admin/ || $1 >= 500 # 匹配 admin 接口的 5xx 错误
布尔模式自然要用到关系运算符,因为它的结果是布尔值
notice
选择语句时,如果写在模式中,在{}前面。如果是用if,那if必须写在{}中
- 要用==而不是=
[4]特殊模式:在数据处理前后使用BEGIN和END模式
有的时候可能需要在处理数据前运行脚本,如为报告创建标题;或是在处理完数据后运行脚本,在awk中分别使用BEGIN和END关键字实现。
这里print命令的结果会直接显示出来,因为BEGIN标示的脚本区域会在读取数据前执行。如果想使用正常的程序脚本处理数据,必须使用另一个脚本区域来定义程序(如果脚本区域过长,可以分成多行输入):
#在结束的时候输出含有admin日志的行数
awk 'BEGIN{c=0}/admin/{c+=1}END{print c}'
[5]空模式:默认所有行,所有列
{2}action行为,也翻译成命令,放在{}中
[1]syntax
- {}也可以嵌套。判断语句可以写在{},也可以写在{}前面作为模式。一个模式对对应一个{}程序脚本,没有模式的{}针对所有行。
- {}内有多条语句时,用;分割
awk '!/^$/{if(NR%3!=0){printf$0"|"}else{print$0}}' test.txt
{3}notice
[1]一个模式只能跟一个行为组合
比如
top | awk 'NR==8{printf $2"$2"}{print "test"}'
NR==8只会对{print $2"$2"}生效,{print “test”}对所有行生效,所以最终结果会有很多行,而不仅仅只有NR==8
的行,除非写在一个大括号{}中。
4.变量
awk支持两种不同类型的变量:内建变量和自定义变量。
属性 | 说明 |
---|---|
$0 | 当前记录(作为单个变量) |
$1~$n | 当前记录的第n个字段,字段间由FS分隔 |
FS | 输入字段分隔符 默认是空格 |
NF | 当前记录中的字段个数,就是有多少列,如果是0,则是空行 |
NR | 已经读出的记录数,就是行号,从1开始,如果有多个文件话,这个值也是不断累加中。 |
RS | 输入的记录他隔符默 认为换行符 |
OFS | 输出字段分隔符 默认也是空格 |
ORS | 输出的记录分隔符,默认为换行符 |
ARGC | 命令行参数个数 |
ARGV | 命令行参数数组 |
FILENAME | 当前输入文件的名字。注意:必须要大写 |
IGNORECASE | 如果为真,则进行忽略大小写的匹配 |
ARGIND | 当前被处理文件的ARGV标志符 |
CONVFMT | 数字转换格式 %.6g |
ENVIRON | UNIX环境变量 |
ERRNO | UNIX系统错误消息 |
FIELDWIDTHS | 输入字段宽度的空白分隔字符串 |
FNR | 当前记录数 |
OFMT | 数字的输出格式 %.6g |
RSTART | 被匹配函数匹配的字符串首 |
RLENGTH | 被匹配函数匹配的字符串长度 |
SUBSEP | \034 |
{1}变量赋值
{ a=$1; b=$0 } # 变量赋值
{ c[$1] = $2 } # 字典赋值
{2}指定行
NR就是行号,从1开始
对 test-awk 文件内容按行做计算sum
name 1th 2th 3th 4th
zhang 123 12 13 14
wang 12 23 34 45
li 11 12 14 17
zhao 12 89 2 3
cat test-awk | awk 'NR==1{printf "%10s %10s %10s %10s %10s %10s\n",$1,$2,$3,$4,$5,"total"}
NR>=2{total = $2+$3+$4+$5;printf "%10s %10s %10s %10s %10s %10s\n",$1,$2,$3,$4,$5,total}'
{3}指定列
awk中默认的字段分隔符是任意的空白字符(例如n个空格或n个制表符tab)
,不是只有空格和制表。
awk会自动给每一行中的每个元素分配一个变量,在默认情况下,会按照下面的方式分配:
$0代表整行
$1代表文本行中的第一个数据字段,$2代表第二个字段,以此类推
NF列数
5.控制语句
{1}选择
{ if ($3 >= 500) { ACTION }
else if ($3 >= 400) { ACTION }
else { ACTION }
}
{ for (i=1; i<=NF; i++) { ACTION } }
{ for (item in c) { ACTION } }
{ exit; } # 结束程序,很少使用
{ next; } # 跳过当前行
{2}循环
统计 user 接口单ip的请求数量超过10次的ip
/user/ { ip=$1; ip_count[ip]++ }
END { for (ip in ip_count) {
if (ip_count[ip] > 10) { print ip, ip_count[ip] }
} }
6.在命令行跨行写awk命令
有3种方法:可以在命令之间加个分号,也可以连续设置多个{},也可以分成多行
$ echo "My name is oldmanw" | awk '{$4="handsomeBoy"; print $0}'
My name is handsomeBoy
$ echo "My name is oldmanw" | awk '{$4="handsomeBoy"}{print $0}'
My name is handsomeBoy
$ echo "My name is oldmanw" | awk '{$4="handsomeBoy"
> print $0}'
My name is handsomeBoy
在使用了表示起始的单引号之后,bash shell会使用次提示符(>)提示你输入更多的数据。你可以在每一行输入一条命令,直到输入了结尾的单引号。注意这种方法只有在控制台输入的时候才有效,> 是自动出现的,如果写在脚本,直接换行即可,不用手动输入>
7.使用脚本文件
$ cat awkScript
{print "The meaning of user " $1 " is " $5}
$ awk -F: -f awkScript /etc/passwd
The meaning of user nobody is Unprivileged User
The meaning of user root is System Administrator
The meaning of user daemon is System Services
The meaning of user _uucp is Unix to Unix Copy Protocol
The meaning of user _taskgated is Task Gate Daemon
The meaning of user _networkd is Network Services
The meaning of user _installassistant is Install Assistant
The meaning of user _lp is Printing Services
The meaning of user _postfix is Postfix Mail Server
以下省略...
9.转义特殊字符
无论在模式中还是在命令中,都可以用八进制的编码来替代单引号和双引号。因为awk的程序用单引号包裹,直接打会误认为是program。
使用引号拼接
使用八进制编码
八进制ASCII码特殊字符列表:
041 ! 042 " 043 # 044 $ 045 % 046 & 047 '
050 ( 051 ) 052 * 053 + 054 , 055 - 056 . 057 /
072 : 073 ; 074 < 075 = 076 > 077 ?
case
打印输出一个单引号
echo -e '\047'
当需要同时打印单引号、双引号和$符等等的时候,该方法比较方便
存在问题,如果特殊字符后面跟数字,则会造成歧义:
#想要输出:单引号 空格 单引号后面跟个1
Administrator@WIN-DFIIOPTMTL8 MINGW64 /e/develop/shell_test
$ echo -e '\047 \0471'
' 9
#第一个正常打印单引号,第二个打印9
#先使用空格分离,然后再使用\b(退格)表示取消前面的空格
Administrator@WIN-DFIIOPTMTL8 MINGW64 /e/develop/shell_test
$ echo -e '\047 \047 \b1'
' '1
简单应用举例:
echo -en 'mysq.host: \047 ';echo -e '192.168.77.10\047'|cat -A
使用\047打印一个单引号,cat -A 最终拼出来一个$符号
echo -en 'mysq.host: \047 \b';echo -e '192.168.77.10\047'|cat -A
单引号后面有一个空格,可以使用\b退格消除掉
su - oracle -c "echo -ne '\047';echo -e '192.168.1.1\047'"
su命令使用双引号引起来,echo 使用单引号引起来,又要打印单引号,则使用\047拼出
su - oracle -c "echo -ne '\042';echo -e '192.168.1.1\042'"
同样道理,使用\042拼双引号
10.函数
https://www.runoob.com/w3cnote/awk-built-in-functions.html#b2
split,gsub,substr
{1}字符串
[1]substr(str, start, l)
start从1开始,l计数时包含start
从第 start 个字符开始长度为 l(包含start) 的子串。如果没有指定 l 的值,返回 str 从第 start 个字符开始的后缀子串。
$ awk 'BEGIN {
str = "Hello, World !!!"
subs = substr(str, 1, 5)
print "Substring = " subs
}'
输出结果为:
Substring = Hello
printf、print print后面默认带换行符,printf没有
格式化字符 | 描述 |
---|---|
s | 字符串 |
c | 单个字符 |
d | 数值 |
e | 指数 |
f | 浮点数 |
g | 根据值决定使用 e 或 f 中较短的输出 |
o | 八进制 |
x | 十六进制 |
% | 百分号 |
$ cat printf-format.awk
BEGIN {
printf "s--> %s\n", "String"
printf "c--> %c\n", "String"
printf "s--> %s\n", 101.23
printf "d--> %d\n", 101,23
printf "e--> %e\n", 101,23
printf "f--> %f\n", 101,23
printf "g--> %g\n", 101,23
printf "o--> %o\n", 0x8
printf "x--> %x\n", 16
printf "percentage--> %%\n", 17
}
$ awk -f printf-format.awk
s--> String
c--> S
s--> 101.23
d--> 101
e--> 1.010000e+02
f--> 101.000000
g--> 101
o--> 10
x--> 10
percentage--> %
print自动换行,printf不换行
printf不受 OFS,ORS 影响
printf 不会使用 OFS 和 ORS,它只根据”format”里面的格式打印数据
字符串长度不足时补 0
默认情况下,右对齐时左边会补空格
$ awk ‘BEGIN { printf “|%5s|\n”, “100” }’
| 100|
为了在右对齐是,左边补 0 (而不是空格),在指定宽度的数字前面加一个 0,即使用”%05s”代 替”%5s”
系统函数
时间函数
11.数组
awk的数组是关联数组,索引不一定是数字。
awk没有数据类型,数字的字符串会根据上下文自动判断为数字还是字符串。数组的索引同样适用,意味着,数字索引不用加引号
awk的数组不需要定义,直接用。名称命名规范和变量相同。
判断数组是否存在某索引
if ( index in array-name )
$ cat array-refer.awk
BEGIN {
#引用了item[55]但没有赋值,缺省为null
x = item[55];
if ( 55 in item )
print "Array index 55 contains",item[55];
item[101]="HD Camcorder";
if ( 101 in item )
print "Array index 101 contains",item[101];
if ( 1010 in item )
print "Array index 1010 contains",item[1010];
}
$ awk -f array-refer.awk
Array index 55 contains
Array index 101 contains HD Camcorder
该例中:
- Item[55] 在引用前没有赋任何值,所以在引用是 awk 自动创建该元素并赋 null 值
- Item[101]是一个已赋值的缘分,所以在检查索引值时返回 true,打印该元素
- Item[1010]不存在,因此检查索引值时,返回 false,不会被打印
循环
for ( var in arrayname )
actions
删除数组元素
如果要删除特定的数组元素,使用 delete 语句。一旦删除了某个元素,就再也获取不到它
的值了。
语法:
delete arrayname[index];
删除数组内所有元素
for (var in array)
delete array[var]
在 GAWK 中,可以使用单个 delete 命令来删除数组的所有元素: Delete array
此外,下面例子中,item[103]=””并没有删除整个元素,仅仅是给它赋了 null 值。
$ cat array-delete.awk
BEGIN {
item[101]="HD Camcorder";
item[102]="Refrigerator";
item[103]="MP3 Player";
item[104]="Tennis Racket";
item[105]="Laser Printer";
item[1001]="Tennis Ball";
item[55]="Laptop";
item["no"]="Not Available";
delete item[102]
item[103]=""
delete item[104]
delete item[1001]
delete item["na"]
for(x in item)
print "Index",x,"contains",item[x]
}
$ awk -f array-delete.awk
Index no contains Not Available
Index 55 contains Laptop
Index 101 contains HD Camcorder
Index 103 contains
Index 105 contains Laser Printer
为数组排序
asort 函数重新为元素值排序,并且把索引重置为从 1 到 n 的值,此处 n 代表数组元素个数。
total = asort(item,itemnew);
用 asorti 为索引排序
12.内置指令
{1}getline
awk是一行一行循环读取的,每读取一行,就会进行一些操作,比如更新NF,NR,FNR和$0等这些内部变量。正常情况下这个过程无法介入。
而getline可以将这些操作给替换成自己想要的操作。
getline顾名思义,获取一行数据,这里的一行指的是当前行的下一行,所以就是获取下一行,然后交给后续处理。这里的下一行,不一定从参数文件中获取,也可以从指定的其他文件、标准输入、shell命令。
==》
[1]作用:可以实现更加灵活的操作
getline可以自由的组合要处理的数据源,让数据源更灵活,比如读一行文件1,再读一行文件2。
getline可以在循环中添加、替换其中的某一次循环、删除某次循环
[2]usage
for(i=1;i++;i<=总行数){
给$0、NR、NF、FNR等变量赋值;
getline
}
getline可以带一个参数,根据有无参数,操作会不同
getline var一个参数时,在读取记录时,会将下一条记录放在参数变量var中,不划分字段不设置变量
- getline var用来处理参数文件,参与循环
- getline var < filename 从其他文件读取数据保存至变量var中,不划分字段不更新内置变量
- commend | getline var #一个参数的getline,将读取的数据存放在变量var1中,不更新变量
#下面例子通过getline得到系统的当前时间。
~]$ awk '{if(("date +\"%F %T\"" | getline var) >0 );print var;close(get_date)}' a.txt ##输出时间
getline无参数时,表示读取下一条记录并保存到$0
中,并且会继续执行后续的代码逻辑,并且进行字段分割,然后继续执行后续的代码逻辑 此时的getline会更新NR,FNR,$0和$N
等内置变量
-
getline无参处理参数文件
-
getline < filename 从其他文件读取数据,并且读取的数据会保存至$0中,会更新$0,NF等内置变量
root@ubuntu:~/bash/awk$ awk '{printf "%s ", $0; getline < "b.txt"; print $0}' a.txt
1 6
2 7
3 8
4 9
5 20
#a.txt文件的内容为上面打印出来的第一列,b.txt文件的内容为上面打印出来的第二列。
- 无参数读取shell命令结果,commend | getline,读取的数据将会被放入$0,会进行变量划分$0,NF等变量
[3]notice
getline是处于awk的main代码块中,如果放在BEGIN或END代码块中则无效
三、notice
1.设置OFS时,要保证有列的操作,比如NF=NF
2.awk中的if判断,用==,shell中的if,可以只用=
3.判断变量同字符串是否相等
经测试,==无效,要用
'{if($1~"文本")}'
awk -F, '{if($NF~"2024-06-11"){print $0}}' 增量客户问题率2024-06-11.csv > zengliangmoban.txt
4.awk的代码用’‘包裹,可以用多个’‘拼接,’'之间和’之间的内容最后会汇总成完整的代码。
5.拼接变量时,跟代码中一样,变量需要""包裹的就加,比如函数参数,但//里的不需要。
6.awk的正则包裹在//中,不支持i和g
7.传入外部变量
一个v一对变量
awk -F, -v get_val=$get_val -v get_point=$get_point -v reportdate=$reportdate -v pt=$pt '{print $1"\t"$2"\t"get_val"\t"get_point"\t"$5"\t"$6"\t"reportdate"\t"pt}' zengliangmoban.txt >> zengliang.txt
8.输出\t
9.回写到文件 > >>
10.awk结果赋给变量 $() ``不行
get_val=$(awk -v min=0.0015 -v max=0.0096 'BEGIN {srand(); print min + rand() * (max - min)}')
四、实例
1.过滤,类似于grep
df -h | grep sd
df -h | awk ‘/sd/’
2.去除重复行列,类似于uniq
uniq -u
awk ‘!a[$0]++’
行count,去除重复行并统计每组数量
$ find -name "*.sh" | xargs awk '/T/{print FILENAME}'|uniq -c|sort -r
3 ./sort_uniq_test.sh
2 ./awk_test.sh
1 ./awk_sub_test.sh
3.计数统计,类似于wc
行
awk xxx | wc -l
列
NF
统计某单词出现次数
# 搜索统计单词“law”的个数
$ awk -F : '/law/{count++} END{print "the count is ",count}' /etc/legal
the count is 1
# 统计单词“the”的个数
$ awk -F : '/the/{count++} END{print "the count is ",count}' /etc/legal
the count is 3
4.取前几行,≈head
5.切分列,≈split
设置输出的分隔符,并且统计分隔符的数量
OFS
Administrator@WIN-DFIIOPTMTL8 MINGW64 /e/develop/shell_test
$ awk -F \t -v OFS='|' '{NF=NF;print $0}' split_test.txt
92000004545_1 1233455444_1 1231-NF-|y 2020-04-20 00:00:00 D U Z 2021-04-20 23:59:59 ANT
#输出的分隔符,可以方便的查看字段的分割情况
Administrator@WIN-DFIIOPTMTL8 MINGW64 /e/develop/shell_test
$ awk -F '\t' -v OFS='|' '{NF=NF;print $0}' split_test.txt
92000004545_1|1233455444_1|1231-NF-ty|2020-04-20 00:00:00|D|U|Z|2021-04-20 23:59:59|A|N|T
#统计下上面分隔符的数量,是一种查看列数的方式,比较直观,当然没有直接输出NF方便
Administrator@WIN-DFIIOPTMTL8 MINGW64 /e/develop/shell_test
$ awk -F '\t' -v OFS='|' '{NF=NF;print $0}' split_test.txt | awk '{s+=gsub(/\|/,"&")}END{print s}'
10
Administrator@WIN-DFIIOPTMTL8 MINGW64 /e/develop/shell_test
$ awk -F '\t' -v OFS='|' '{NF=NF;print $0}' split_test.txt | grep -o '|' | wc -l
10
循环所有列,并且输出单引号'和双引号"
的文本
#单引号:用双引号框住,在使用2个单引号框住需要输出的单引号,在使用反斜线对单引号转义。前面2个单引号和后面2个单引号是并列、前后串行的。
ls | awk '{print " ' \' ' "}'
#双引号:使用双引号框住,在使用反斜线对双引号转义
ls | awk '{print " \" "}'
#单引号"\047" 使用单引号的八进制编码,需要用""包裹
awk '{for(i=1,i<=NF;i++){printf "\047"$i"\047";}}' 5.txt
#双引号"\042" 使用双引号的八进制编码,需要用""包裹
awk '{for(i=1,i<=NF;i++){printf "\047"$i"\042";}}' 5.txt
case
将hive命令行中查出的一行字段值转为select列表,如果使用默认分隔符(n个空白),有些时间字段值形如 2020-04-20 00:00:00
,因为中间有个空格,会把这个值分为2个,但实际上列之间的分隔符是\t,字段里面的分隔符是空白。所以此时不需要用默认分隔符,要指明-F ‘\t’。
$ awk '{print NF}' 5.txt
326
$ awk -F '\t' '{print NF}' 5.txt
321
- 也可以用循环
在for循环体中进行判断,判断一个字符串中是否包含子串可以用index函数。
写复杂awk时,可以先把架子搭好,然后从内往外分别写。
awk '{for(i = 1;i <= NF;i++){if(index($i,"2020-04-20")==0){print "\047"$i}else if(index($i,"00:00:00")==0){print " "$i"\047,"}else{print "\047"$i"\047,"}}}'
或者
awk '{for(i=1;i <= NF;i++){printf "\047$i\047"}}' | awk '{gsub(/0\047,\0420/,"0 0");printf $0}'
或者
awk '{for(i=1;i<=NF;i++){if($(i+1)~/[0-9][0-9]:[0-9][0-9]:[0-9][0-9]/){print $i" "$(i+1);i+=1}else {print $i}}}'
#测试文本
echo "92000004545_1 1233455444_1 1231-NF-ty 2020-04-20 00:00:00 D U Z 2021-04-20 23:59:59 A N K"|
用管道的话,awk接收的要是一行行的数据。如果是文件名,要用xargs
将2个指定关键字的行之间的内容输出,且不包括关键字行
http://blog.chinaunix.net/uid-29369535-id-5767318.html
将hive show create 的输出中过滤出字段列表
SLF4j:123
SLF4j:456
22/06/17
22/06/17
22/06/17
CREATE
CREATE
CREATE
CREATE TABLE
`col1` string,
`col2` string,
`col3` string
COMMENT 'comment text'
PARTITIONED BY (
`col_par`)
ROW
ROW
COMMENT
因为分区那一行也有COMMENT,所以要用^COMMENT
cat hive_create_table.sh |xargs \
awk '/^CREATE TABLE/,/^ROW/{if(!sub(/CREATE|^COMMENT|PARTITIONED|ROW/,"")){gsub(/`|\)/,"",$1);print $1}}'
指定输出的列
不想要哪列,直接将那列设为"",然后输出$0即可
- 输出$1之后的列
awk '{ $1=""; print $0 }' file_name
6.将ldap的add文件修改为modify文件
从ldapsearch中把dn那行过滤出来,然后在每行dn下插入修改的2个属性。
ldapsearch -x -D cn=admin,dc=xyyh,dc=com -w bigdata2019 -b ou=People,dc=xyyh,dc=com | grep "dn: uid=" | \
awk '{print $0 "\nchangetype: modify\nreplace: userPassword\nuserPassword: Q1w2e3r4\n"}' \
>/opt/ldap/user_modify_changeDefaultPassword.ldif
7.将文件中的空白行提取出,一个空白行打印一句话
/^$/表示空行
awk '/^$/ {print "Blank line}' test.txt
8.将文件中空行过滤,然后每3行合并为一行
空行:!/^$/ 或者NF,NF表示当前行字段数,为0表示空行,NF==0
awk NF file.txt > new.txt
打印换行:printf表示不换行,print会自动换行
awk '!/^$/{if(NR%3!=0){printf$0"|"}else{print$0}}' test.txt
9. 查找
打印包含主机名的行,因为没有指定动作指令,默认动作为打印。
awk '/nologin/' /etc/passwd
或者
#注意:必须要用xargs,不然就是在文件路径名里找
find -name "*.sh" |xargs awk '/TTT/ {print $0}'
10. 替换
{1}sub/gsub/gensub
awk的sub/gsub函数用来替换字符串
sub匹配第一次出现的符合模式的字符串,相当于 sed ‘s//’
gsub匹配所有的符合模式的字符串,相当于 sed ‘s//g’
sub(/regexp/, replacement, target);简称 gsub(r,s,t)
注意第三个参数target,如果忽略
则使用$0
作为参数,即整行文本。
[1]case
(1)base
awk '{ sub(/AAAA/,"BBBB"); print $0 }' t.txt
awk '{ gsub(/AAAA/,"BBBB"); print $0 }' t.txt
(2)将目录下所有文件都替换且覆盖原文件(用FILENAME变量)
find . -name "*.sh" |xargs awk '{gsub(/AA/,"TT");print $0 > FILENAME}'
{2}if
awk -F. -v OFS='.' '{if($2==2020)$2=2021}{print$0}'
11. 某一列进行条件判断
cat /etc/hosts | awk ‘$3~/(z|d|m)[0-9]/ {print $1}’ | grep -v ‘161|159|163’
将hosts列表中的第三列是’z3’这种格式并且把161、159、163这3个排除。
注意:在模式部分中,如果要单独对某一列进行判断,用$num~
,然后后面跟正则,正则一定要用/ xxx /
包裹
12. 从某目录下过滤出某些文件,然后将这些文件复制后改名
#!/bin/bash
arr=`ll -lrt /sftp/data | grep 20201207 | awk '{print $9}'`
for i in $arr
do
cp -f $i `echo $i | awk -F. -v OFS='.' '{if($2==20201207)$2=20201208}{print $0}'`
done
13.逆转字符串
用for循环
echo abc12345678 | awk '
{for(i=1;i<=length;i++){line=substr($0,i,1) line}} END {print line}
'
- length即当前行长度
- substr($0,i,1),表示取当前字符从索引i开始,取当前位
- line=substr($0,i,1) line;后面的line表示将值分别保存在内存栈中,循环完毕后打印的就是所有行
substr($3,6,2)—>表示从第三个字段里的第六个字符开始,截取2个字符结束
substr($3,6)—>表示是从第3个字段里的第6个字符开始,一直到结尾
14.将文本以行为单位逆序输出
awk '{line[NR]=$0}END{i=NR;while(i>0){print line[i];i--}}'
15.打印指定行的前几行&按行逆序
case:打印匹配行的前2行
awk 'NR==1,/d/{print $0}' shell-awk-test.txt | awk '{line[NR]=$0}END{i=NR;while(i>0){print line[i];i--}}'|sed -n '1,+2p' |awk '{line[NR]=$0}END{i=NR;while(i>0){print line[i];i--}}'