书写格式
1. BEGIN { statements }
2. END { statements }
3. expression { statements }
4. /regular expression1/ { statements }
5. compound pattern { statements }
6. pattern1 , pattern2 { statements }
# expressions, with constants, variables, assignments, function calls, etc.
print expression-list
printf (format, expression-list)
if (expression) statement
if (expression) statement else statement
while (expression) statement
for (expression ; expression ; expression) statement
for (variable in array) statement
do statement while (expression)
break
continue
next
exit
exit expression
{ statements }
传递外部变量 -v
传入多个变量使用多个-v
awk -v host=$HOSTNAME 'BEGIN{print "hostname is: "host}' ==> hostname is: hellokitty
echo 1,2 |awk -F',' -v OFS='|' '{print $1,$2}'
awk中使用shell中的变量
一:"'$var'"
这种写法大家无需改变用'括起awk程序的习惯,是老外常用的写法。如:
var="test"
awk 'BEGIN{print "'$var'"}' # awk 'BEGIN{print "' $var '"}'
这种写法其实际是双括号变为单括号的常量,传递给了awk。
如果var中含空格,为了shell不把空格作为分格符,便应该如下使用:
var="this is a test"
awk 'BEGIN{print "'"$var"'"}' # awk 'BEGIN{print "' "$var" '"}'
二:'"$var"' #未测试成功
这种写法与上一种类似。如果变量含空格,则变为'""$var""'较为可靠。 awk 'BEGIN{print '"$var"' }' # awk 'BEGIN{print ' ""$var"" ' }'
三:export变量,使用ENVIRON["var"]形式,获取环境变量的值
例如:
var="this is a test"; export var;
awk 'BEGIN{print ENVIRON["var"]}'
四:可以使用awk的-v选项 (如果变量个数不多,个人偏向于这种写法)
例如:
var="this is a test"
awk -v awk_var="$var" 'BEGIN {print awk_var}'
这样便把系统变量var传递给了awk变量awk_var。
awk向shell变量传递值
“由awk向shell传递变量”,其思想无非是用awk(sed/perl等也是一样)输出若干条shell命令,然后再用shell去执行这些命令。
eval $(awk 'BEGIN{print "var1='str1';var2='str2'"}')
或者eval $(awk '{printf("var1=%s; var2=%s; var3=%s;",$1,$2,$3)}' abc.txt)
之后可以在当前shell中使用var1,var2等变量了。
echo "var1=$var1 ----- var2=$var2"
获取环境变量
awk 'BEGIN { print ENVIRON["USER"] }' == > root
awk脚本 -f scripfile :
将awk执行体放入独立的文件
$ cat main.awk
/101/{print "\047 Hello! \047"} #\047代表单引号
{print $1,$2}
{total=$4+$5+$6;
avg=total/3;
print $1,avg;}
$ echo 1,2 |awk -F',' -v OFS='|' -f main.awk
允许间隔正则表达式的使用,如 [[:alpha:]]
-W re-interval or --re-inerval :允许间隔正则表达式的使用
awk里调用系统命令
$ echo 1504170547|awk '{"date -d @\""$1"\" +%F-%T"|getline xxx;print xxx}'
2017-08-31-17:09:07
echo 1464259591**xiaoming**2|awk -F'**' '{IFS="**";OFS="**";"date" " " "-d" " \"@" $1 "\" " "+%F-%T"|getline xxx;print xxx,$2,$3}'
2016-05-26-18:46:31**xiaoming**2
内置变量
awk 'BEGIN{print ARGC}' 1 2 ==>3 #参数个数: 'awk' 为第0个参数
awk 'BEGIN{for (i in ARGV) print ARGV[i]}' 12 34 #参数字典,从0开始
awk #第0个参数
12 #第1个参数
34 #第2个参数
awk '{print ARGIND,$0}' 10.txt 15.txt #当前文件的位置(从1开始算),将会在每行打印文件位置
awk 'ARGIND==1 {a[$0]} ARGIND>1&&!($0 in a) {print $0}' a b #查找在b中,但不在a中的行
awk 'ARGIND==1 {a[$0]}
ARGIND>1 && !($0 in a) {print $0}' one two #拆行的写法
ERRNO 最后一个系统错误的描述。
FIELDWIDTHS 字段宽度列表(用空格键分隔)。
FILENAME 当前文件名。
FNR 同NR,但相对于当前文件。
FS 字段分隔符(默认是任何空格)。
IGNORECASE 如果为真,则进行忽略大小写的匹配。
NF 当前记录中的字段数。
NR 当前记录数。
OFMT 数字的输出格式(默认值是%.6g)。
OFS 输出字段分隔符(默认值是一个空格)。
ORS 输出记录分隔符(默认值是一个换行符)。
RLENGTH 由match函数所匹配的字符串的长度。
RS 记录分隔符(默认是一个换行符)。
RSTART 由match函数所匹配的字符串的第一个位置。
SUBSEP 数组下标分隔符(默认值是/034)。
格式化
awk 'BEGIN{print CONVFMT}' ==> %.6g
awk 'BEGIN {
printf "CONVFMT=%s, num=%f, str=%s\n", CONVFMT, 12.11, 12.11 }'
==> CONVFMT=%.6g, num=12.110000, str=12.11
awk 'BEGIN {
CONVFMT="%d"; // 通过更改CONVFMT,我们可以定义自己的转换格式:
printf "CONVFMT=%s, num=%f, str=%s\n", CONVFMT, 12.11, 12.11 }'
==> CONVFMT=%d, num=12.110000, str=12
三元表达式
awk '{max = ($1 > $3) ? $1: $3: print max}' test
正则的写法:
a: /re/ #正则写在//中 使用~ , !~,不能用 =、<、>等 ;如:$0 ~ /[0-9]+$/
b: BEGIN { digits = "A[0-9]+$" } $2 ~ digits
BEGIN {
sign = "[+-]?"
decimal= "[0-9]+[.]?[0-9]*"
fraction= "[.][0-9]+"
exponent= "([eE]" sign "[0-9]+)?" #空格表示拼接
number= "^" sign "(" decimal "|" fraction ")" exponent "$" #拼接了一个很长的正则
} $0 ~ number
c: $0 ~ /(\+|-)[0-9]+/ 等价于 $0 ~ "(\\+|-)[0-9]+"
gawk专用正则表达式元字符,不适合unix版本的awk。
(1)\Y :匹配一个单词开头或者末尾的空字符串。
(2)\B:匹配单词内的空字符串。
(3)\<:匹配一个单词的开头的空字符串,锚定开始。
(4)\> :匹配一个单词的末尾的空字符串,锚定末尾。
(5)\w :匹配一个字母数字组成的单词。
(6)\W :匹配一个非字母数字组成的单词。
(7)\‘:匹配字符串开头的一个空字符串。
(8)\' :匹配字符串末尾的一个空字符串。
元字符
\b backspace
\f formfeed
\n newline Oine feed)
\r carriage return
\t tab
\ddd octal value ddd, where ddd is 1 to 3 digits between 0 and 7
\c any other character c literally (e.g., \\ for backslash, \" for ") #得到原始字符
流程控制
# if - else
awk '$2 > 5 { n = n + 1; pay = pay + $2 * $3 ;print $0,$2 * $3 }
END { if (n > 0)
print n, "employees, total pay is", pay,
"average pay is", pay/n #输出可以写在多行,且不必用续行符
else
print "no employees are paid more than $6/hour"
}' text
=>
Mark 11.00 20 220
Mary 5.50 22 121
2 employees, total pay is 341 average pay is 170.5
# while
awk '{i = 1
while (i <= 2) {
printf("\t%.2f\n", $2 * (1 + $2) ^i )
i = i + 1
}
}'
#do ... while ...
{
count=1
do {
print "I get printed at least once no matter what"
} while ( count != 1 )
}
#for
awk '{for (i = 1; i <= $3; i =i + 1) printf("\t%.5f\n", $1 * (1 + $2) ^ i) }'
0.1 0.1 3 #分别是: $1 $2 循环次数
0.11000
0.12100
0.13310
1 2 3
3.00000
9.00000
27.00000
数组array
awk '{line[NR]=$0 } #将每一行加入line数组 删除array项: delete fooarray[1]
END { i = NR
while (i > 0) {
print line[i]
i = i - 1
}
}' text
可改写为for: END{ for (i = NR; i > 0; i = i - 1) print line[i] }
换行
awk中换行等同于分号; 用\ 转移换行实现续行.
awk '{print #换行意味着结束,将会输出整个文件内容
$1 #这里识别为单独的变量,已经超出上边print的控制范围
$2}' text #这条执行等同于 awk '{print ; $1;$2}' test
awk '{print \
$1
$2}' text #输出了每一列, $2并没有输出 awk '{print $1;$2}' test
awk '{print \ #续行
$1, #,表示这一行还没结束
$2}' text #输出了预计的结果 awk '{print $1,$2}' test
awk '{print
$1, #添加逗号导致了报错
$2}' text # awk '{print ; $1, ; $2}' test # 逗号后跟上了分号,导致语法错误 //不是很好理解
九九乘法表
seq 9 | sed 'H;g' | awk -v RS='' '{for(i=1;i<=NF;i++)printf("%dx%d=%d%s", i, NR, i*NR, i==NR?"\n":"\t")}'
== >
1x1=1
1x2=2 2x2=4
1x3=3 2x3=6 3x3=9
1x4=4 2x4=8 3x4=12 4x4=16
1x5=5 2x5=10 3x5=15 4x5=20 5x5=25
1x6=6 2x6=12 3x6=18 4x6=24 5x6=30 6x6=36
1x7=7 2x7=14 3x7=21 4x7=28 5x7=35 6x7=42 7x7=49
1x8=8 2x8=16 3x8=24 4x8=32 5x8=40 6x8=48 7x8=56 8x8=64
1x9=9 2x9=18 3x9=27 4x9=36 5x9=45 6x9=54 7x9=63 8x9=72 9x9=81
执行系统命令、管道
16、awk 'BEGIN {"date"|getline d; print d}'
通过管道把date的执行结果送给getline,并赋给变量d,然后打印。
通过getline命令交互输入name,并显示出来。
awk 'BEGIN {system("echo \"Input your name:\"");
getline d;
print "\nYour name is",d,"\b!\n"}' #\b退格
#从文件读取内容
awk 'BEGIN {FS=":"; while(getline< "/etc/passwd" >0) { if($1~"050[0-9]_") print $1}}'
awk 'BEGIN {FS=":";OFS="\t"; while(getline< "/etc/passwd" >0) { if($3>500) print $3,$1}}'
==> 65534 nobody
1000 aaa
内建函数:
#参考:
http://www.cnblogs.com/chengmo/archive/2010/10/08/1845913.html
http://www.cnblogs.com/mywolrd/archive/2012/04/27/2472916.html
length(s): length("nihao")=>5 , awk '{print length($1),$0}' text
atan2(y,x) arctangent of y/x in the range -π to π
cos(x) 指数e的x次方
exp(x)
int(x)
log(x)
rand() 随机数[0,1)
sin(x)
sqrt(x)
srand(x) 关于x的随机数
index(s,t) #返回s中字符串t的第一位置 #第一个字符索引为0
awk 'BEGIN {print index("Bunny","ny")}' =>4
length(s) #返回s长度
split(s,a,fs) #在fs上将s分成序列a
match(s,r) #测试s是否包含匹配r的字符串
sprintf(fmt,exp) #返回经fmt格式化后的exp
sub(r,s) #用$0中最左边最长的子串代替s ,该字符串是r
gsub(r,s) #在整个$0中用s替代r
gsub(r,s,t) #在整个t中用s替代r
substr(s,p) #返回字符串s中从p开始的后缀部分
substr(s,p,n) #返回字符串s中从p开始长度为n的后缀部分
tolower(s) #返回字符串并且将所有字符转换成小写
toupper(s) #返回字符串并且将所有字符转换成大写
#gsub函数详解:
gsub(regular expression, subsitution string, target string);简称 gsub(r,s,t) ==>
a: 返回替换的次数0-~ 是一个condition statement(0为假,其他为真)
b: t为指定的字段或者省略($0)
c: 同时会做替换的操作
# echo "a b c 2011-11-22 a:d" | awk 'gsub(/-/,"",$4){print $4}' #可以看出gsub是条件语句
==> 20111122
# echo "a b c 2011-11-22 a:d" | awk 'gsub(/-/,"",$4)' #省略action时,默认输出该行
==> a b c 20111122 a:d
# echo "a b c 2011-11-22 a:d" | awk '$4=gsub(/-/,"",$4)'
==> a b c 2 a:d #由于gsub返回次数,所以这里$4变成了2
awk 'gsub(/abc/,"x"){cost++;print $0} END {print "The total is $" cost " filename"}' 15 #匹配/0/的行,将/x/替换为x,打印出替换后的该行, 并统计次数
awk 'BEGIN{print ""}gsub(/.+/,"BB",$2)gsub(/.+/,"AA",$1) #两个gsub之间如果添加;将会输出两遍
{if ($4>1000&&$4<2000) c1+=$4;
else if ($4>2000&&$4<3000) c2+=$4;
else if ($4>3000&&$4<4000) c3+=$4;
else c4+=$4; }
END {printf "c1=[%d];c2=[%d];c3=[%d];c4=[%d]\n",c1,c2,c3,c4}' text
split (string, array, field separator)
#将string按照分隔符得到一个array(相当与python list),下标0,1,2,3...
#field separator可选,默认为FS
echo "12:34:56"| awk '{split($0,a,":");for (i in a) print i,a[i]}'
substr(s,p,n) 返回字符串s中从p开始长度为n的后缀部分
#n可选,不指定时为到结尾
substr($3,10,8) ---> 表示是从第3个字段里的第10个字符开始,截取8个字符结束.
substr($3,6) ---> 表示是从第3个字段里的第6个字符开始,一直到结尾
awk 'BEGIN {print index("Bunny","ny")}' =>4
match
$ awk 'BEGIN {print match("ANCD", /d/)}' => 0
$ awk 'BEGIN {print match("ANCD", /C/)}' => 3
$ awk 'BEGIN {print match("Bunny","ny")}' => 4
$ awk '$1=="Lulu" {print match($1, "u")} datafile => 4 #
$ awk 'BEGIN {print match('xabcd','ab')}' => 1 #单引号时也会返回,暂时不知怎么回事
printf sprintf的区别:
awk 'BEGIN{printf("%d",12)}' #直接输出
awk 'BEGIN{print sprintf("%d",12)}' #不会输出,需要使用print
格式化规则: http://www.cnblogs.com/mywolrd/archive/2012/04/27/2472916.html
#通过exit在某条件时退出,但是仍执行END操作。
awk '{gsub(/\$/,"");gsub(/,/,""); #gsub放入了{}中,就会只仅仅替换(因为只是返回了两个数字,没有实际输出的操作)
if ($4>3000&&$4<4000) exit;
else c4+=$4; }
END {printf "c1=[%d];c2=[%d];c3=[%d];c4=[%d]\n",c1,c2,c3,c4}' file
通过next在某条件时跳过该行,对下一行执行操作。
awk '{gsub(/\$/,"");gsub(/,/,"");
if ($4>3000) next;
else c4+=$4; }
END {printf "c4=[%d]\n",c4}' file
# 重定向输出>
1. 把file1、file2、file3的文件内容全部写到fileall中,格式为打印文件并前置文件名。
awk '{ print FILENAME,$0 }' file1 file2 file3 >fileall
2. 把合并后的文件重新分拆为3个文件。并与原文件一致。
awk ' $1 != previous { close(previous); previous=$1 }
{print substr($0,index($0," ") +1) >$1}' fileall # >追加
#时间、时间戳互转
awk 'BEGIN{print mktime("2017 12 10 1 1 1"),strftime("%F %T",1512838861)}'
awk '{gsub("-"," ",$1); print mktime($1" 0 0 0")}' a.TXT
awk '{s=gensub(/file_|\.log/,"","g",FILENAME);sub("_"," ",s);cmd1="date -d \""s"\" \"+%s\"";cmd1|getline t;cmd2="date -d @"t++" \"+%F %T\"";cmd2|getline T;print T,$0}' file
引号问题
在awk中调用系统变量必须用单引号,如果是双引号,则表示字符串
awk引用外部变量
http://hi.baidu.com/liheng_2009/item/6466a4c0e087222447d5c0c8
以下两个链接给了更多的讨论:
http://www.linuxsir.org/bbs/thread121709.html
http://bbs.chinaunix.net/thread-1381166-1-1.html
Flag=abcd
$ awk 'BEGIN{print "$Flag"}' 结果为$Flag
以下四种方式都可以正常引用
$ awk 'BEGIN{print "'"$Flag"'"}' 结果为abcd #变量两边各添加一个单引号(该单引号用双引号引起来)
$ awk 'BEGIN{print "'$flag'"}'
$ awk "BEGIN{print \"$Flag\"}"
$ awk BEGIN\{print\""$Flag"\"\}
$ awk BEGIN\{print\"$flag\"\}
$ awk '{print $1,$2,myname}' - v myname=$name myfile
详解: awk是要运行在shell环境中的。所以,写在awk中的命令,要先经过shell解析后,再交由awk来解释和执行。
$ str=Hello
$ awk 'BEGIN{print " '$str' "}'
=> Hello
# 看上去是双引号套单引号,其实真正的原因为:
# 这是shell的功能,shell对单引号和双引号,按从左到右的顺序成对匹配
# awk命令用单引号引起来,就是防止shell对其中内容进行解释
awk '{print " '$str' "}' file
实际上就是2部分
1: awk '{print " '
2: '"}'
即awk对2个单引号内的命令起作用。
至于$str就被shell正常解释为变量str的值。
所以,如果str=hello,则经解释后成为,awk {print "hello"} file
而如果str=hello world,则解释时,在解释前一部分:awk {print " 后,在替换了变量后,变成了hello world,当shell读到hello和world中间的空格时,认为这是IFS,于是,把他们放在于不同的域中,这样解释成了:
awk BEGIN{print "hello
world"}两部分。
按照上面的解释,就可以这么来修改,比如
a)$ awk 'BEGIN{print " ' "$a" ' "}'
或者
b)$ awk "BEGIN{print \"$a\"}"
或者
c)$ awk BEGIN\{print\""$a"\"\}
对于a,解释成为:
awk BEGIN{print "hello world"} #因为$a在替换后,还在“”中包括中,所以当成了一个字符串处理。
自定义函数(可以在''内的任意位置)
function function_name(argument1, argument2, ...)
{
function body # 可以return
}
awk '
function b()
{
print "b.in.$1="$1; #来源与file的第一列
}
{
v=100; y=200
print "a.in.v="v; #
print "a.in.y="y;
a(y)b()
print "a.out.v="v;
print "a.out.y="y;
}
function a(y) #带参数
{
print "(a)v="v; #全局参数v
v=v+$1+y; # 100 + 200 + 123
y=300; #局部的y更改不会影响到主题
print "更改后的v: " v
print "更改后的y: " y
}' file
a.in.v=100
a.in.y=200
(a)v=100
更改后的v: 423
更改后的y: 300
b.in.$1=123 #$1来自于文件
a.out.v=423
a.out.y=200 #y的值并没有变化
函数内定义的变量,不会传递给主题
变量v的值在函数a中发生了变化,并体现在了主函数中
变量y是作为函数的a的形参,在函数a中对y的修改无法体现在主函数中
函数在调用时要用(),并且可以不加分号。
awk '$1 > 5 && $2 < 10' test # && 、 ||
awk '{if(NF>1){$1="";print $0}}' test.txt # 去第一列
awk '/hello1/,/hello2/ {print $2}' file #处理第一次出现hello到第一次出现 hello2之间的行的内容
awk '{print $1$2}' file <==> awk '{print $1 $2}' file #$1与$2之间的空格可以省略,输出的样式中将合并
netstat -ant|awk '/^tcp/ {++a[$NF]} END {for (i in a) print i,a[i]}' # a[$NF]+=1
awk 'BEGIN{print "\""}' #输出双引号
awk 'BEGIN{print "'\''"}' #输出单引号
awk 'BEGIN{print "\047Hello!\047"}' #输出单引号(ascii码八进制是47)
awk 'BEGIN {max = 0} {if ($1>max) max=$1 fi} END {print "Max=", max}' data #求最大值,不严谨(负数?)
{ field = $NF} END { print field } #最后一行的最后一列
{ nf= nf + NF } END { print nf } #统计域数
{ for (i = NF; i > 0; i = i - 1) printf("%s ", $i) ; printf ( "\n" ) } #倒序每一行的域
{sum=0;for (i=1;i<=NF;i++) sum=sum+$i; print $0 " sum is: " sum } #计算每一行的和
{ for (i = 1; i <= NF; i = i + 1) sum= sum+ $i } END { print sum } #计算每一行每一列的sum
{for (i=1;i<=NF;i++) if($i <0) $i=-$i; print } #将每一列的负数取相反数
awk 'FNR <= 3 {print FILENAME ": " $0}' num text #读取每个文件的前3行
echo a b c d|awk 'BEGIN {one=1;two=2} {print $(one+two)}' #打印了$3, 注意这里运算的写法
awk '$3 > 10 { print $0} ; $2>4.2 {print $0}' text #多条件; 分号都可以省略
#统计文本
{ nc = nc + length($0) + 1
nw = nw + NF
}
END {print NR, "lines," nw, "words,", nc, "Characters" }
=> 6 lines, 18 words, 77 characters
#字符串的比较
'$1 == "Susie" ' #字符串要用双引号
echo 2 3 1|awk '/[3-9]/ {print $0}' #/正则/, 检测整行是否匹配
用for和if显示日期
awk 'BEGIN {
for(j=1;j<=12;j++) {
flag=0;
printf "\n%d月份\n",j;
for(i=1;i<=31;i++)
{
if (j==2&&i>28) flag=1; #flag来控制输出,会有一定的浪费
if ((j==4||j==6||j==9||j==11)&&i>30) flag=1;
if (flag==0) {printf "%02d.%02d ",j,i}
}
}
}'
实操: 统计当天每个ip访问的时长
#cat file
211.103.220.197 - - [28/Aug/2009:16:56:16 +0800] 'GET /dyanmicStat?time=16:58:57 ' 200 9 0.019
211.103.220.197 - - [28/Aug/2009:16:56:18 +0800] 'GET /dyanmicStat?time=16:58:59 ' 200 9 0.019
211.103.220.197 - - [28/Aug/2009:16:56:20 +0800] 'GET /dyanmicStat?time=16:59:01 ' 200 9 0.019
211.103.220.197 - - [28/Aug/2009:16:56:22 +0800] 'GET /dyanmicStat?time=16:59:03 ' 200 9 0.019
58.33.241.108 - - [29/Aug/2009:17:26:11 +0800] 'GET /index ' 200 20845 0.120
58.33.241.108 - - [29/Aug/2009:17:26:14 +0800] 'GET /dyanmicStat?time=17:28:53 ' 200 9 0.016
58.33.241.108 - - [29/Aug/2009:17:26:16 +0800] 'GET /dyanmicStat?time=17:28:55 ' 200 9 0.017
58.33.241.108 - - [29/Aug/2009:17:26:18 +0800] 'GET /dyanmicStat?time=17:28:57 ' 200 9 0.017
58.33.241.108 - - [29/Aug/2009:17:26:20 +0800] 'GET /dyanmicStat?time=17:28:59 ' 200 9 0.018
awk '
BEGIN{
FS=" +|[[]";
print "IP地址\t\t\t访问时间\t\t\t\t访问时长"
}
function cal(){
split(s_time,M,":");
split(e_time,N,":");
time=(N[2]-M[2])*60*60+(N[3]-M[3])*60+N[4]-M[4];
if (ip)
print ip"\t"s_time"-"e_time"\t"int(time/60)"分"time%60"秒"
}
$1 !=ip {
cal();
ip=$1;s_time=$5}
{e_time=$5}
END{
cal()
}
' file
==>
IP地址 访问时间 访问时长
211.103.220.197 28/Aug/2009:16:55:14-28/Aug/2009:16:56:22 1分8秒
58.33.241.108 29/Aug/2009:17:26:11-29/Aug/2009:17:34:01 7分50秒
评论