8. 文件长度相加
在目录中查看文件时,如果想快速查看所有文件的长度及其总和,但要排除子目录,使用ls -l命令,然后管道输出到a w k,a w k首先剔除首字符为d(使用正则表达式)的记录,然后将文件长度列相加,并输出每一文件长度及在E N D部分输出所有文件的长度。
本例中,首先用ls -l命令查看一下文件属性。注意第二个文件属性首字符为d,说明它是一个目录,文件长度是第5列,文件名是第9列。如果系统不是这样排列文件名及其长度,应适时加以改变。
下面的正则表达式表明必须匹配行首,并排除字符d,表达式为^ [ ^ d ]。
使用此模式打印文件名及其长度,然后将各长度相加放入变量t o t中。
 
[sam@chenwy sam]$ ls -l | awk '/^[^d]/ {print $9"\t"$5} {tot+=$5} END {print "total KB:" tot}'
 
...................
 
total KB:174144
 
或者:ls -l | awk '$1 !~ /^d/ {print $0"\t"$5} {tot+=$5} END {print "total KB:" tot}'
内置的字符串函数
  1. awk内置字符串函数
  2. g s u b ( r, s ) 在整个$ 0中用s替代r
  3. g s u b ( r, s , t ) 在整个t中用s替代r
  4. i n d e x ( s , t ) 返回s中字符串t的第一位置
  5. l e n g t h ( s ) 返回s长度
  6. m a t c h ( s , r ) 测试s是否包含匹配r的字符串
  7. s p l i t ( s , a , f s ) 在f s上将s分成序列a
  8. s p r i n t ( f m t , e x p ) 返回经f m t格式化后的e x p
  9. s u b ( r, s ) 用$ 0中最左边最长的子串代替s
  10. s u b s t r ( s , p ) 返回字符串s中从p开始的后缀部分
  11. s u b s t r ( s , p , n ) 返回字符串s中从p开始长度为n的后缀部分
复制代码
g s u b函数有点类似于s e d查找和替换。它允许替换一个字符串或字符为另一个字符串或字符,并以正则表达式的形式执行。第一个函数作用于记录$ 0,第二个g s u b函数允许指定目标,然而,如果未指定目标,缺省为$ 0。
i n d e x(s,t)函数返回目标字符串s中查询字符串t的首位置。l e n g t h函数返回字符串s字符长度。
m a t c h函数测试字符串s是否包含一个正则表达式r定义的匹配。s p l i t使用域分隔符f s将字符串s划分为指定序列a。
s p r i n t函数类似于p r i n t f函数(以后涉及),返回基本输出格式f m t的结果字符串e x p。
s u b(r,s)函数将用s替代$ 0中最左边最长的子串,该子串被( r)匹配。
s u b(s,p)返回字符串s在位置p后的后缀。s u b s t r(s,p,n)同上,并指定子串长度为n。
现在看一看a w k中这些字符串函数的功能。
1. gsub
要在整个记录中替换一个字符串为另一个,使用正则表达式格式, /目标模式/,替换模式/。例如改变学生序号4 8 4 2到4 8 9 9:
  1. [root@Linux_chenwy root]# cd /usr/sam
  2. [root@Linux_chenwy sam]# awk 'gsub(/4842/,4899){print $0}' grade.txt
  3. J.Troll 07/99 4899 Brown-3 12 26 26
复制代码
  1. [root@Linux_chenwy sam]# awk 'gsub(/4842/,4899)' grade.txt
  2. J.Troll 07/99 4899 Brown-3 12 26 26
复制代码
2. index
查询字符串s中t出现的第一位置。必须用双引号将字符串括起来。例如返回目标字符串B u n n y中n y出现的第一位置,即字符个数。
  1. [root@Linux_chenwy sam]# awk 'BEGIN {print index("Bunny","ny")}' grade.txt
  2. 4
复制代码
3. length
返回所需字符串长度,例如检验字符串J . Tr o l l返回名字及其长度,即人名构成的字符个数
  1. [root@Linux_chenwy sam]# awk '$1=="J.Troll" {print length($1)" "$1}' grade.txt
  2. 7 J.Troll
复制代码
还有一种方法,这里字符串加双引号。
  1. [root@Linux_chenwy sam]# awk 'BEGIN{print length("A FEW GOOD MEN")}'
  2. 14
复制代码
4. match
m a t c h测试目标字符串是否包含查找字符的一部分。可以对查找部分使用正则表达式,返回值为成功出现的字符排列数。如果未找到,返回0,第一个例子在A N C D中查找d。因其不存在,所以返回0。第二个例子在A N C D中查找D。因其存在,所以返回A N C D中D出现的首位置字符数。第三个例子在学生J . L u l u中查找u。
  1. [root@Linux_chenwy sam]# awk 'BEGIN{print match("ANCD",/d/)}'
  2. 0
  3. [root@Linux_chenwy sam]# awk 'BEGIN{print match("ANCD",/D/)}'
  4. 4
  5. [root@Linux_chenwy sam]# awk '$1=="J.Lulu" {print match($1,"u")}' grade.txt
  6. 4
复制代码
5. split
使用s p l i t返回字符串数组元素个数。工作方式如下:如果有一字符串,包含一指定分隔符- ,例如A D2 - K P 9 - J U 2 - L P - 1,将之划分成一个数组。使用s p l i t,指定分隔符及数组名。此例中,命令格式为( " A D 2 - K P 9 - J U 2 - L P - 1 ",p a r t s _ a r r a y," - "),s p l i t然后返回数组下标数,这里结果为4。
  1. [root@Linux_chenwy sam]# awk 'BEGIN {print split("123-456-789",pats_array,"-")}'3
复制代码
还有一个例子使用不同的分隔符。
  1. [root@Linux_chenwy sam]# awk 'BEGIN {print split("123#456#789",myarray,"#")}'                                                        3
复制代码
这个例子中,s p l i t返回数组m y a r r a y的下标数。数组m y a r r a y取值如下:
  1. myarray[1]=123
  2. myarray[2]=456
  3. myarray[3]=789
复制代码
结尾部分讲述数组概念。
6. sub
使用s u b发现并替换模式的第一次出现位置。字符串S T R包含‘poped popo pill’,执行下列s u b命令s u b(/ o p /," o p ",S T R)。模式o p第一次出现时,进行替换操作,返回结果如下:‘pO Ped pope pill’。
如:学生J . Tr o l l的记录有两个值一样,“目前级别分”与“最高级别分”。只改变第一个为2 9,第二个仍为2 4不动,操作命令为s u b(/ 2 6 /," 2 9 ",$ 0),只替换第一个出现2 4的位置。注意J . Tr o l l记录需存在。
  1. [root@Linux_chenwy sam]# awk '$1=="J.Troll" sub(/26/,"29",$0)' grade.txt
  2. M.Tans 5/99 48311 Green 8 40 44
  3. J.Lulu 06/99 48317 green 9 24 29
  4. P.Bunny 02/99 48 Yellow 12 35 28
  5. J.Troll 07/99 4842 Brown-3 12 29 26
  6. L.Tansl 05/99 4712 Brown-2 12 30 28
复制代码
7. substr
s u b s t r是一个很有用的函数。它按照起始位置及长度返回字符串的一部分。例子如下:
  1. [root@Linux_chenwy sam]# awk '$1=="L.Tansl" {print substr($1,1,3)}' grade.txt
  2. L.T
复制代码
上面例子中,指定在域1的第一个字符开始,返回其前面5个字符。
如果给定长度值远大于字符串长度, a w k将从起始位置返回所有字符,要抽取L Ta n s l - e y的姓,只需从第3个字符开始返回长度为7。可以输入长度9 9,a w k返回结果相同。
  1. [root@Linux_chenwy sam]# awk '$1=="L.Tansl" {print substr($1,1,99)}' grade.txt
  2. L.Tansl
复制代码
s u b s t r的另一种形式是返回字符串后缀或指定位置后面字符。这里需要给出指定字符串及其返回字串的起始位置。例如,从文本文件中抽取姓氏,需操作域1,并从第三个字符开始:
  1. [root@Linux_chenwy sam]# awk '{print substr($1,3)}' grade.txt
  2. Tans
  3. Lulu
  4. Bunny
  5. Troll
  6. Tansl
复制代码
还有一个例子,在B E G I N部分定义字符串,在E N D部分返回从第t个字符开始抽取的子串。
  1. [root@Linux_chenwy sam]# awk 'BEGIN{STR="A FEW GOOD MEN"}END{print substr(STR,7)}' grade.txt
  2. GOOD MEN
复制代码
8. 从s h e l l中向a w k传入字符串
a w k脚本大多只有一行,其中很少是字符串表示的。大多要求在一行内完成a w k脚本,这一点通过将变量传入a w k命令行会变得很容易。现就其基本原理讲
述一些例子。
使用管道将字符串s t a n d - b y传入a w k,返回其长度。
  1. [root@Linux_chenwy sam]# echo "Stand-by" | awk '{print length($0)}'
  2. 8
复制代码
设置文件名为一变量,管道输出到a w k,返回不带扩展名的文件名。
  1. [root@Linux_chenwy sam]# STR="mydoc.txt"
  2. [root@Linux_chenwy sam]# echo $STR|awk '{print substr($STR,1,5)}'
  3. mydoc
复制代码
设置文件名为一变量,管道输出到a w k,只返回其扩展名。
  1. [root@Linux_chenwy sam]# STR="mydoc.txt"
  2. [root@Linux_chenwy sam]# echo $STR|awk '{print substr($STR,7)}'
  3. txt
printf修饰符
  1. - 左对齐
  2. Wi d t h 域的步长,用0表示0步长
  3. . p r e c 最大字符串长度,或小数点右边的位数
  4. 表9-7 awk printf格式
  5. % c A S C I I字符
  6. % d 整数
  7. % e 浮点数,科学记数法
  8. % f 浮点数,例如(1 2 3 . 4 4)
  9. % g a w k决定使用哪种浮点数转换e或者f
  10. % o 八进制数
  11. % s 字符串
  12. % x 十六进制数  

按同样方式使用a w k得到同样结果。

  1. [sam@chenwy sam]$ awk 'BEGIN{printf "%c\n",65}'
  2. A

2. 格式化输出

打印所有的学生名字和序列号,要求名字左对齐, 1 5个字符长度,后跟序列号。注意\ n换行符放在最后一个指示符后面。输出将自动分成两列。

  1. [root@chenwy sam]# awk '{printf "%-15s %s\n",$1,$3}' grade.txt
  2. M.Tans          48311
  3. J.Lulu          48317
  4. P.Bunny         48
  5. J.Troll         4842
  6. L.Tansl         4712
复制代码
加入一些文本注释帮助理解报文含义。可在正文前嵌入头信息。注意这里使用p r i n t加入头信息。如果愿意,也可使用p r i n t f。
  1. [root@chenwy sam]# awk 'BEGIN{print "Name\t\tS.Number"}{printf "%-15s %s\n",$1,$3}' grade.txt
  2. Name            S.Number
  3. M.Tans          48311
  4. J.Lulu          48317
  5. P.Bunny         48
  6. J.Troll         4842
  7. L.Tansl         4712
复制代码
3.向一行a w k命令传值
在查看a w k脚本前,先来查看怎样在a w k命令行中传递变量。
在a w k执行前将值传入a w k变量,需要将变量放在命令行中,格式如下:
  1. awk 命令变量=输入文件值
复制代码
(后面会讲到怎样传递变量到a w k脚本中)。
下面的例子在命令行中设置变量A G E等于1 0,然后传入a w k中,查询年龄在1 0岁以下的所有学生。
  1. [root@chenwy sam]# awk '{if ($5<AGE) print $0}' AGE=10 grade.txt
  2. M.Tans 5/99 48311 Green 8 40 44
  3. J.Lulu 06/99 48317 green 9 24 26
复制代码
要快速查看文件系统空间容量,观察其是否达到一定水平,可使用下面a w k一行脚本。因为要监视的已使用空间容量不断在变化,可以在命令行指定一个触发值。首先用管道命令将df -k 传入a w k,然后抽出第4列,即剩余可利用空间容量。使用$ 4 ~ / ^ [ 0 - 9 ] /取得容量数值(1 0 2 4块)而不是d f的文件头,然后对命令行与‘ i f ( $ 4 < T R I G G E R )’上变量T R I G G E R中指定
的值进行查询测试。
  1. [root@chenwy sam]# df -k|awk '{if($4<TRIGGER) print $6"\t"$4}' TRIGGER=560000
  2. /boot   458589
  3. /dev/shm        99352
复制代码
  1. [root@chenwy sam]# df -k|awk '($4~/^[0-9]/) {if($4<TRIGGER) print $6"\t"$4}' TRIGGER=5600000
  2. /       2610716
  3. /boot   458589
  4. /dev/shm        99352
复制代码
($4~/^[0-9]/)好像没什么用
在系统中使用df -k命令,产生下列信息:
  1. [root@chenwy sam]# df -k
  2. 文件系统               1K-块        已用     可用 已用% 挂载点
  3. /dev/sda2              5162828   2289804   2610764  47% /
  4. /dev/sda1               497829     13538    458589   3% /boot
  5. none                     99352         0     99352   0% /dev/shm
复制代码
如果系统中d f输出格式不同,必须相应改变列号以适应工作系统。
当然可以使用管道将值传入a w k。本例使用w h o命令, w h o命令第一列包含注册用户名,这里打印注册用户,并加入一定信息。
  1. [sam@chenwy sam]$ who |awk '{print $1" is logged on"}'
  2. root is logged on
  3. root is logged on
  4. [sam@chenwy sam]$ who
  5. root     :0           Nov 23 20:17
  6. root     pts/0        Nov 23 20:25 (:0.0)
复制代码
a w k也允许传入环境变量。下面的例子使用环境变量HOME支持当前用户目录。可从pwd命令管道输出到a w k中获得相应信息。
  1. [sam@chenwy sam]$ pwd | awk '{if ($1==derr) print $1}' derr=$HOME
  2. /usr/sam
复制代码
4. awk脚本文件
可以将a w k脚本写入一个文件再执行它。命令不必很长(尽管这是写入一个脚本文件的主要原因),甚至可以接受一行命令。这样可以保存a w k命令,以使不必每次使用时都需要重新输入。使用文件的另一个好处是可以增加注释,以便于理解脚本的真正用途和功能。
使用前面的几个例子,将之转换成a w k可执行文件。像原来做的一样,将学生目前级别分相加awk ‘(t o t + = $ 6) END{print "club student total points:" t o t }’ g r a d e . t x t。
创建新文件s t u d e n t _ t o t . a w k,给所有a w k程序加入a w k扩展名是一种好习惯,这样通过查看文件名就知道这是一个a w k程序。文本如下:
  1. [sam@chenwy sam]$ cat student_tot.awk
  2. #!/bin/awk -f
  3. #all commnet lines must start with a hash '#'
  4. #name:students_tots.awk
  5. #to call:student_tot.awk grade.txt
  6. #prints total and average of club student points
  7.  
  8. #print a header first
  9. BEGIN{
  10. print "Student  Date  Member  No.  Grade Age  Points Max"
  11. print "Name     Joined                        Gained  Point Available"
  12. print "=============================================================="
  13. }
  14. #let's add the scores of points gained
  15. (tot+=$6)
  16.  
  17. #finished proessing now let's print the total and average point
  18. END{
  19. print "Club student total points :" tot
  20. print "Average Club Student Points:" tot/NR}
复制代码
通过将命令分开,脚本可读性提高,还可以在命令之间加入注释。这里加入头
信息和结尾的平均值。基本上这是一个一行脚本文件。
执行时,在脚本文件后键入输入文件名,但是首先要对脚本文件加入可执行权限。
  1. [sam@chenwy sam]$ chmod u+x student_tot.awk
  2. [sam@chenwy sam]$./student_tot.awk grade.txt
  3. Student  Date  Member  No.  Grade Age  Points Max
  4. Name     Joined                        Gained  Point Available
  5. ==============================================================
  6. M.Tans 5/99 48311 Green 8 40 44
  7. J.Lulu 06/99 48317 green 9 24 26
  8. P.Bunny 02/99 48 Yellow 12 35 28
  9. J.Troll 07/99 4842 Brown-3 12 26 26
  10. L.Tansl 05/99 4712 Brown-2 12 30 28
  11. Club student total points :155
  12. Average Club Student Points:31
复制代码

5. 在a w k中使用F S变量

[sam@Linux_chenwy sam]$ cat passwd.awk

#!/bin/awk -f

#to call:passwd.awk /etc/passwd

#print out the first and seventh fields

BEGIN{

FS=":"}

{print $1,"\t",$7}

6. 向a w k脚本传值
向a w k脚本传值与向a w k一行命令传值方式大体相同,格式为:

  1. awk script_file var=value input_file
复制代码
下述脚本对比检查文件中域号和指定数字。这里使用了N F变量M A X,表示指定检查的域号,使用双引号将域分隔符括起来,即使它是一个空格。
脚本如下:
  1. [sam@Linux_chenwy sam]$ cat fieldcheck.awk
  2. #!/bin/awk -f
  3. #check on how many fields in a file
  4. #name:fieldcheck.awk
  5. #to call:fieldcheck MAX=n FS=<separator> filename
  6. #
  7. NF!=MAX{
  8. print("line" NR " does not have " MAX "fields")}
复制代码

如果NF中的值不等于最大MAX值,则打印出"哪一行的域总数不是max"

如果以/ e t c / p a s s w d作输入文件(p a s s w d文件有7个域),运行上述脚本。参数格式如下:
  1. [sam@Linux_chenwy sam]$ chmod u+x fieldcheck.awk
  2. [sam@Linux_chenwy sam]$ ./fieldcheck.awk MAX=7 FS=":" passwd
复制代码

正好7个域,如果改成6,就会显示不同结果,试试看?

使用前面一行脚本的例子,将之转换成a w k脚本如下:
  1. [sam@Linux_chenwy sam]$ cat name.awk
  2. #!/bin/awk -f
  3. #name:age.awk
  4. #to call:age.awk AGE=n grade.txt
  5. #print ages that are lower than the age supplied on the comand line
  6. {if ($5<AGE)
  7. print $0}
复制代码
文本包括了比实际命令更多的信息,没关系,仔细研读文本后,就可以精确知道其功能及如何调用它。
不要忘了增加脚本的可执行权限,然后将变量和赋值放在命令行脚本名字后、输入文件前执行。
  1. [sam@Linux_chenwy sam]$ chmod u+x name.awk
  2. [sam@Linux_chenwy sam]$ ./name.awk AGE=10 grade.txt
  3. M.Tans 5/99 48311 Green 8 40 44
  4. J.Lulu 06/99 48317 green 9 24 26
复制代码

同样可以使用前面提到的管道命令传值,下述a w k脚本从d u命令获得输入,并输出块和字节数。
  1. [root@Linux_chenwy sam]# cat duawk.awk
  2. #!/bin/awk -f
  3. #to call:du|duawk.awk
  4. #prints file/direc's in bytes and blocks
  5. BEGIN{
  6. OFS="\t";
  7. print "name" "\t\t","bytes","blocks\n"
  8. print "==============================="}
  9. {print $2,"\t\t",$1*512,$1}
复制代码
执行:
  1. [root@Linux_chenwy sam]# du | ./duawk.awk
  2. name                    bytes   blocks
  3.  
  4. ===============================
  5. ./.kde/Autostart                                6144    12
  6. ./.kde                          8192    16
  7. ./.xemacs                               4096    8
  8. ./sam                           2048    4
  9. ./dir1                          2048    4
  10. ./file6                         2048    4
  11. .                               94208   184
复制代码