awk命令(语言)

一、awk概述

1.为什么使用awk

awk 是一种程序语言它具有一般程序语言常见的功能. 因awk语言具有某些特点, 如 : 使用直译器(Interpreter)不需先行编译; 变量无类型之分(Typeless), 可使用文字当数组的下标(Associative Array)...等特色. 因此, 使用awk撰写程序比起使用其它语言更简洁便利且节省时间. awk还具有一些内建功能, 使得awk擅于处理具有数据行(Record), 字段(Field)型态的资料; 此外, awk内建有pipe的功能, 可将处理中的数据传送给外部的 Shell命令加以处理, 再将Shell命令处理后的数据传回awk程序, 这个特点也使得awk程序很容易使用系统资源.

由于awk具有上述特色在问题处理的过程中可轻易使用awk来撰写一些小工具这些小工具并非用来解决整个大问题,它们只扮演解决个别问题过程的某些角色可藉由Shell所提供的pipe将数据按需要传送给不同的小工具进行处理以解决整个大问题这种解题方式使得这些小工具可因不同需求而被重复组合及重用(reuse); 也可藉此方式来先行测试大程序原型的可行性与正确性将来若需要较高的执行速度时再用C语言来改写.这是awk最常被应用之处若能常常如此处理问题读者可以以更高的角度来思考抽象的问题而不会被拘泥于细节的部份.

2.如何工作

为便于解释awk程序架构及有关术语(terminology), 先以一个员工薪资档(emp.dat ), 来加以介绍.

cat emp.dat
A125 Jenny 100 210
A341 Dan 110 215
P158 Max 130 209
P148 John 125 220
A123 Linda 95 210

文件中各字段依次为 员工ID, 姓名薪资率, 实际工时. ID中的第一码为部门识别码. "A","P"分别表示"组装""包装"部门本小节着重于说明awk程序的主要架构及工作原理并对一些重要的名词辅以必要的解释由这部分内容读者可体会出awk语言的主要精神及awk与其它语程序言的差异处为便于说明以条列方式说明于后.

名词定义:

a. 数据行 : awk从数据文件上读取数据的基本单位以上列文件emp.dat为例, awk读入的

第一笔数据行是 "A125 Jenny 100 210"

第二笔数据行是 "A341 Dan 110 215"

一般而言一个 数据行就相当于数据文件上的一行资料. (参考 : 内建变量"RS" )

b.字(Field)为数据行上被分隔开的子字符串.

以数据行"A125 Jenny 100 210"为例,

第一栏 第二栏 第三栏 第四栏 "A125" "Jenny" 100 210

一般是以空白字符来分隔相邻的字段. ( 参考 : 内建变量"FS" )

附:内建字段变量

$0                 # 一字符串, 其内容为目前 awk 所读入的数据行.
$1                 # $0 上第一个字段的数据.
$2                 # $0 上第二个字段的数据.
...

 awk 从数据文件中读取一个数据行时, awk 会使用内建变量$0 予以记录.每当 $0 被改动时 (例如 : 读入新的数据行  自行变更 $0,...) awk 会立刻重新分析 $0 的字段情况并将 $0 上各字段的数据用 $1, $2, ..予以记录.

c. Patternawk 可接受许多不同型态的 Pattern. 一般常使用 "关系表达式"(Relational expression) 作为pattern.

例如: x > 34 是一个 Pattern, 判断变量 x 与 34 是否存在大于的关系.

附:常见pattern类型

BEGIN   END
>, <, >=, <=, ==, !=, ^指数                           #关系运算符,与c基本一致
~ (match)   !~  (not match)
/^[0-9]*$/ { print "This line is a integer !" }      #正则表达式    
FNR >= 23 && FNR <= 28 { print "     " $0 }          #混合pattern
FNR == 23 , FNR == 28 { print "     " $0 }           
#Pattern1 , Pattern2 遇到这种 Pattern, awk 会帮您设立一个 switch(或flag), Pattern1成立时打开 , Pattern2成立时关闭. 此例效果同上

d. Actions : Actions 是由许多awk指令构成awk的指令与 C 语言中的指令十分类似.

例如 : awk的I/O指令 : print, printf( ), getline... ,流程控制指令 : if(...){..} else{..}, while(...){...}...

附:常用指令(statement)

表达式 ( function calls, assignments..)
print 表达式列表
printf( 格式化字符串, 表达式列表)
if( 表达式 ) 语句 [else 语句]
while( 表达式 ) 语句
do 语句 while( 表达式)
for( 表达式; 表达式; 表达式) 语句
for( variable in array) 语句
delete             #释放数组中元素所占用的内存空间
break
continue
next                        #掠过其后所有指令,接着读取下一个 Record
exit [表达式]
语句

e. 内建变量(Built-in Variables)

awk 提供了许多内建变量使用者于程序中可使用这些变量来取得相关信息常见的内建变量有 :

NF (Number of Fields)            #为一整数, 其值表$0上所存在的字段数目.
NR (Number of Records)           #为一整数, 其值表awk已读入的数据行数目.
FNR                              #与 NR 功用类似. 不同的是awk每打开一个新的文件,FNR 便从 0 重新累计
FILENAME                         #awk正在处理的数据文件文件名.
ARGC
#表示命令行上除了选项 -F, -v, -f 及其所对应的参数之外的所有参数的个数.若将"awk程式"直接写到命令列上, 则 ARGC 亦不将该"程式部分"列入计算.
ARGV                             #ARGV数组用以记录命令列上的参数.
FS                               #栏位分隔字符.
OFS                              #输出时的栏位分隔字符. 预设值 " "(一个空格)
ORS                              #输出时数据行的分隔字符. 预设值 "\n"(换行)
OFMT                             #数值资料的输出格式. 预设值 "%.6g"(打印6位小数)
RS
#( Record Separator) : awk从文件上读取资料时, 将根据 RS 的定义把资料切割成许多Records,而awk一次仅读入一个Record,以进行处理.

 f. 数组

awk程序中允许使用字符串当做数组的下标(index). 利用这个特色十分有助于资料统计工作.(使用字符串当下标的数组称为Associative Array). 使用数组前不须宣告数组名及其大小.

 g.内建函数

# 以下为字符串函数
index( 原字串, 找寻的子字串 )
#若原字串中含有欲找寻的子字串,则返回该子字串在原字串中第一次出现的位置,若未曾出现该子字串则返回0.
length( 字串 )        #返回该字串的长度.
match( 原字串, 用以找寻比对的正则表达式 )
#awk会在原字串中找寻合乎正则表达式的子字串. 若合乎条件的子字串有多个, 则以原字串中最左方的子字串为准. awk找到该字串后会依此字串为依据进行下列动作:
设定awk內建变量 RSTART, RLENGTH :
    RSTART =  合条件的子字串在原字串中的位置.(未找到0)
    RLENGTH = 合条件的子字串长度.(未找到-1)
返回 RSTART 之值.
split( 原字串, 数组名称, 分隔字符 )
#awk将依所指定的分隔字符(field separator)来分隔原字串成一个个的栏位(field),并以指定的数组(下标从1开始)记录各个被分隔的栏位.
sprintf(格式字符串, 项1, 项2, ...)
sub( 比对用的正则表达式, 将替換的新字串, 原字串 )
#将原字串中第一个(最左边)合乎所指定的正则表达式的子字串改以新字串取代. 第二个参数"将替換的新字串"中可用"&"来代表"合於条件的子字串"
gsub( 比对用的正则表达式, 将替換的新字串, 原字串 )
#这个函数与 sub()一样,同样是进行字串取代的函数. 唯一不同点 gsub()会取代所有合条件的子字串. gsub()会返回被取代的子字串个数.
substr( 字串,起始位置 [,长度] ):
#返回从起始位置起,指定长度的子字串. 若未指定长度,则返回起始位置到字串末尾的子字串.

# 以下为数学函数
int(x)           #返回x的整数部分(去掉小数).
sqrt(x)          #返回x的平方根.
exp(x)          # 将返回e的x次方.
log(x)          #将返回x以e为底的对数值.
sin(x)           # x 须以弧度为单位,sin(x)将返回x的sin函数值.
cos(x)          # x 须以弧度为单位,cos(x)将返回x的cos函数值
atan2(y,x)        # 返回 y/x 的tan反函数之值,返回值系以弧度为单位.
rand()           # 返回介于 0与1之间的(近似)随机数值; 0 < rand()<1.
srand([x])        # 指定以x为rand( )函数起始的种子.

h. 工作流程

执行awk它会反复进行下列四步骤.

  1. 自动从指定的数据文件中读取一个数据行.
  2. 自动更新(Update)相关的内建变量之值 : NF, NR, $0...
  3. 依次执行程序中 所有  Pattern { Actions } 指令.
  4. 当执行完程序中所有 Pattern { Actions } 若数据文件中还有未读取的数据则反复执行步骤1到步骤4.

awk会自动重复进行上述4个步骤使用者不须于程序中编写这个循环 (Loop).

 i.使用系统资源

awk 提供与 UNIX 用法近似的 pipe, 其记号亦为 "|". 其用法及含意如下 :

[a. 语法] awk output指令 | "Shell命令"     #( 如 : print $1,$2 | "sort -k 1" )
[b. 语法] "Shell命令" | awk input指令      #( 如 : "ls" | getline)

在 语法中 awk所输出的数据将转送往 Shell ,  Shell 的命令进行处理以上例而言, print 所输出的数据将经由 Shell 命令 "sort -k 1" 排序后再送往屏幕(stdout).

上例, "print$1, $2" 可能反复执行很多次其输出的结果将先暂存于 pipe ,等到该程序结束时才会一并进行 "sort -k 1". "sort -k 1" 的执行次数是一次. 

 b 语法中, awk将先调用 Shell 命令其执行结果将通过 pipe 送入awk程序, 就上例而言, awk先让 Shell 执行 "ls", Shell 执行后将结果存于 pipe, awk指令 getline再从pipe 中读取数据.

使用本语法时应留心以上例而言,awk "立刻"调用 Shell 来执行 "ls", 执行次数是一次. getline 则可能执行多次(若pipe中存在多行数据).

除上列 a, b 两种语法外, awk程序中其它地方如出现像 "date", "clear", "ls"... 这样的字符串, awk只把它当成一般字符串处理. 

二、格式及使用(不同版本awk,nawk,mawk,gawk)

Pattern1 { Actions1 }

Pattern2 { Actions2 }

......

awk 会先判断 (Evaluate)  Pattern 的值 Pattern 判断后的值为true (或不为0的数字,或不是空的字符串),  awk将执行该 Pattern 所对应的 Actions. 反之 Pattern 之值不为 true, awk将不执行该 Pattern所对应的 Actions.

例如 : awk程序中有下列两指令

50 > 23 {print "Hello! The word!!" }
"banana" ~ /123/ { print "Good morning !" }

awk会先判断 50 >23 是否成立因为该式成立所以awk将印出"Hello! The word!!". 而另一 Pattern  "banana" ~/123/, 因为"banana" 内未含有任何子字符串可 match /123/,  Pattern 之值为false, awk将不会印出 "Good morning !"

有时语法 Pattern { Actions }, Pattern 部分被省略,只剩 {Actions}. 这种情形表示 "无条件执行这个 Actions".

3.执行方式(三种)

例:打印出文件 today_rpt1  today_rpt2 的内容

awk '{print}' today_rpt1 today_rpt2          #方式一,直接写在 Shell 的命令行上, 这种方式仅适合较短的awk程序.
awk -f mydump.awk today_rpt1 today_rpt2      #方式二,将awk程序写入到文件中
#!/bin/sh
#方式三,建立 shell 脚本
# 注意 awk 与 ' 之间须有空白隔开
awk '{print}' $*

注:awk程序中一律以"括住字符串或字符而不使用 ' , 以免与 Shell 混淆.

三、使用举例

1.打印文件中指定的字段数据并加以计算. 

以文件 emp.dat 为例计算每人应发工资并打印报表.

awk '{ print $2, $3 * $4 }' emp.dat
awk '{ printf("%-6s Work hours: %3d Pay: %5d\n", $2, $3, $3* $4) }' emp.dat

注:Pattern 部分被省略表无任何限制条件printawk所提供的输出指令会将数据输出到stdout(屏幕). print 的参数间彼此以 "," (逗号隔开印出数据时彼此间会以空白隔开. (参考:内建变量OFS). awk中也提供与 C 语言中类似用法的 printf() 函数使用该函数可进一步控制数据的输出格式.

2.选择符合指定条件的记录. 

组装部门员工调薪5%,(组装部门员工之ID以"A"开头). 所有员工最后之薪资率若仍低于100, 则以100计.

awk '$1 ~ /^A.*/ { $3 *= 1.05 } $3<100 { $3 = 100 }  { printf("%s %8s %d\n", $1, $2, $3)}' emp.dat

3.数组及循环的使用

首先建立一个数据文件并取名为 reg.dat. 此为一学生注册的资料文件第一栏为学生姓名其后为该生所修课程.

cat reg.dat
Mary O.S. Arch. Discrete
Steve D.S. Algorithm Arch.
Wang Discrete Graphics O.S.
Lisa Graphics A.I.
Lily Discrete Algorithm

统计各科修课人数,并印出结果:

cat course.awk
{for( i=2; i <= NF; i++) 
    Number[$i]++
}
END{for(course in Number) 
        printf("%10s %d\n", course, Number[course] )
}
awk -f course.awk reg.dat 
  Graphics 2
      O.S. 2
  Discrete 3
      A.I. 1
      D.S. 1
     Arch. 2
 Algorithm 2

注:数组默认值为 0. END 成立(其值为true)的条件是: "awk处理完所有数据即将离开程序时 ". 唯有当awk读完所有数据时Actions才会被执行 ( 注意不管数据行有多少笔, END仅在最后才成立故该Actions仅被执行一次.) BEGIN  END 有点类似awk中另一个保留的Pattern.

4.使用shell命令

awk程序中允许呼叫Shell指令并提供管道解决awk与系统间数据传递的问题所以awk很容易使用系统资源读者可利用这个特点来编写某些适用的系统工具.

写一个 awk 程序(count.awk)来打印出在线用户数.

BEGIN {
    while ( "who" | getline ) 
        n++
    print n
}
awk -f count.awk

注:awk 程序并不一定要处理数据文件以本例而言仅输入程序文件count.awk, 未输入任何数据文件.

awk提供另一个调用Shell命令的方法即使用awk函数system("shell命令"),如:

BEGIN {
    system("date > date.dat")
    getline < "date.dat"
    print "Today is ", $2, $3
}

但使用 system( "shell 命令" ) , awk无法直接将执行中的部分数据输出给Shell 命令 Shell 命令执行的结果也无法直接输入到awk.

5.综合程序

本节将示范一个统计上班到达时间及迟到次数的程序. 该程序每日被执行时将读入两个文件:员工当日到班时间的数据文件 (如下列之 arr.dat) 和 存放员工当月迟到累计次数的文件.

某公司其员工到勤时间档如下取名为 arr.dat. 文件中第一栏为员工代号第二栏为到达时间本范例中将使用该文件为数据文件. 

1034 7:26
1025 7:27
1101 7:32
1006 7:45
1012 7:46
1028 7:49
1051 7:51
1029 7:57
1042 7:59
1008 8:01
1052 8:05
1005 8:12

a.在到班数据文件 arr.dat 之前增加一行标题, 并产生报表输出到文件 today_rpt1中

BEGIN { 
    file = "today_rpt1"
    print " ID Number Arrival Time" > file
    print "===========================" > file
}
{ printf(" %s %s\n", $1, $2) > file }
 ID Number Arrival Time
===========================
 1034 7:26
 1025 7:27
 1101 7:32
 1006 7:45
 1012 7:46
 1028 7:49
 1051 7:51
 1029 7:57
 1042 7:59
 1008 8:01
 1052 8:05
 1005 8:12

注:awk程序中文件名称 file 的前后须以" (双引号)括住表示为一字符串常量若未以"括住 today_rpt1 将被awk解释为一个变量名称本程序中若使用 ">" 将数据重导到 today_rpt1, awk 第一次执行该指令时会产生一个新文件 today_rpt1, 其后再执行该指令时则把数据追加到today_rpt1文件末, 并非每执行一次就重开一个新文件. 若采用">>"其差异仅在第一次执行该指令时, 若已存在today_rpt1则 awk 将直接把数据 append 在原文件末尾. 这一点, 与UNIX中的用法不同.

 b.将数据按员工ID排序后再输出到文件 today_rpt2 , 并于表头附加执行时的日期.

BEGIN {
    file = "today_rpt2"
    "date" | getline           # Shell 执行 "date", getline 取得结果并以$0记录
    print " Today is " , $2, $3 > file
    print "=========================" > file
    print " ID Number Arrival Time" > file
    close( file )
}
{ printf(" %s %s\n", $1 ,$2 ) | "sort -k 1 >> today_rpt2" }

注:awk 的输入指令 getline, 每次读取一列数据. 若getline之后未接任何变量, 则所读入之资料将以$0 记录, 否则以所指定的变量储存之. getline 一次读取一行资料若读取成功则return 1,若读取失败则return -1, 若遇到文件结束(EOF), return 0.

本程序中 printf() 指令会被执行多但读者不用担心数据被重复sort12awk结束该程序时才会 close 这个 pipe , 此时才将这12行数据一次送往系统,并呼叫 "sort -k 1 >> today_rpt2" 处理之.

 c.若八点为上班时间请加注 "*"于迟到记录之前并计算平均上班时间. 

#!/bin/sh
awk '
BEGIN {
    FS= "[ \t:]+"                #改变字段切割的方式,可匹配多次
    file = "today_rpt3"
    "date" | getline 
    print " Today is " ,$2, $3 > file
    print "=========================" > file
    print " ID Number Arrival Time" > file
    close( file )
}
{
    #已更改字段切割方式, $2表到达小时数, $3表分钟数
    arrival = HM_to_M($2, $3)
    printf(" %s %s:%s %s\n", $1, $2, $3, arrival > 480 ? "*": " " ) | "sort -k 1 >> today_rpt3"
    total += arrival
}
END {
    close("today_rpt3")
    close("sort -k 1 >> today_rpt3")
    printf(" Average arrival time : %d:%d\n", total/NR/60, (total/NR)%60) >> "today_rpt3"
}
function HM_to_M( hour, min ){
    return hour * 60 + min
}
' $*

注:字段分隔字符 FS (field seperator) awk的内建变量,其默认值是空白及tab. awk每次切割字段时都会先参考FS 的内容awk 中亦允许使用者自定函数函数定义方式请参考本程序, function  awk 的保留字.

指令 close( "sort -k 1 >> today_rpt3" ), 其意思为关闭程序中置于 "sort -k 1 >> today_rpt3 " 之前的 Pipe , 并立刻调用 Shell 来执行"sort -k 1 >> today_rpt3". (若未执行这指令, awk必须于结束该程序时才会进行上述动作;则这12sort后的数据将被追加到文件 today_rpt3 中 "Average arrival time : ..." 的后方)

因为 Shell 排序后的数据也要写到 today_rpt3, 所以awk必须先关闭使用中的today_rpt3 以使 Shell 正确将排序后的数据追加 today_rpt3. 否则2个不同的 process 同时打开一个文件进行输出将会产生不可预期的结果. 

 d.从文件中读取当月迟到次数并根据当日出勤状况更新迟到累计数.(按不同的月份累计于不同的文件)

cat 09月late.dat
1012 0
1006 1
1052 2
1034 0
1005 0
1029 2
1042 0
1051 0
1008 0
1101 0
1025 1
1028 0
#!/bin/sh
 
awk '
BEGIN {
    Sys_Sort = "sort -k 1 >> today_rpt4"
    file = "today_rpt4"
    FS = "[ \t:]+"
    "date" | getline
    print " Today is " , $2, $3 > file
    print "=========================" > file
    print " ID Number Arrival Time" > file
    close( file )
    
    # 从文件按中读取迟到数据, 并用数组cnt[ ]记录. 数组cnt[ ]中以
    # 员工代号为下标, 所对应的值为该员工之迟到次数.
    late_file = $2"late.dat"
    while( getline < late_file >0 ) 
        cnt[$1] = $2
    close( late_file )
}
{
    # 已更改字段切割方式, $2表小时数,$3表分钟数
    arrival = HM_to_M($2, $3)
    if( arrival > 480 ){
        mark = "*" # 若当天迟到,应再增加其迟到次数, 且令mark 为"*".
        cnt[$1]++ 
    }
    else 
        mark = " "
     
    # message 用以显示该员工的迟到累计数, 若未曾迟到message为空字符串
    message = cnt[$1] ? cnt[$1] " times" : ""
    printf("%s %2d:%2d %5s %s\n", $1, $2, $3, mark, message ) | Sys_Sort
    total += arrival
}
END {
    close( file )
    close( Sys_Sort )
    printf(" Average arrival time : %d:%d\n", total/NR/60, (total/NR)%60 ) >> file
    
    #将数组cnt[ ]中新的迟到数据写回文件中
    for( any in cnt )
        print any, cnt[any] > late_file
}
function HM_to_M( hour, min ){
    return hour*60 + min
}
' $*
 Today is  04月 10日
=========================
 ID Number Arrival Time
1005  8:12     * 1 times
1006  7:45       
1008  8: 1     * 1 times
1012  7:46       
1025  7:27       
1028  7:49       
1029  7:57       
1034  7:26       
1042  7:59       
1051  7:51       
1052  8: 5     * 1 times
1101  7:32       
 Average arrival time : 7:49

6.处理多行数据(改变RS)

RS 的默认值是 "\n", 故平常awk中一行数据就是一个 Record.  RS = "" :连续的空白行仅被视成一个单一的Record Saparator.  且awk会略过文件头或文件尾的空白行.

张长弓
GNUPLOT 入门



吴国强
Latex 简介
VAST-2 使用手册
mathematic 入门


李小华
awk Tutorial Guide
Regular Expression
#!/bin/sh
awk '
BEGIN {
    RS = ""
    FS = "\n"    
    split( "一. 二. 三. 四. 五. 六. 七. 八. 九.", order, " " )
}
{
    printf("\n%s 报告人 : %s \n", order[NR], $1)
    for( i=2; i <= NF; i++) 
        printf(" %d. %s\n", i-1, $i)
}
' $*
一. 报告人 : 张长弓 
 1. GNUPLOT 入门

二. 报告人 : 吴国强 
 1. Latex 简介
 2. VAST-2 使用手册
 3. mathematic 入门

三. 报告人 : 李小华 
 1. awk Tutorial Guide
 2. Regular Expression

7.读取命令行参数

#!/bin/sh
awk '
BEGIN {
for( i=0; i<ARGC ; i++)
    print ARGV[i]               # 依次印出awk所记录的参数
}
' "$@"

注:ARGC : 为一整数. 代表命令行上, 除了选项-v, -f 及其对应的参数之外所有参数的数目. ARGV[] 为一字符串数组. ARGV[0],ARGV[1],...ARGV[ARGC-1], 分别代表命令行上相对应的参数.

8.与用户交互

apple 苹果
orange 柳橙
banana 香蕉
pear 梨子
starfruit 杨桃
bellfruit 莲雾
kiwi 奇异果
pineapple 菠萝
watermelon 西瓜
#!/bin/sh
awk '
BEGIN {
    while( getline < ARGV[1] ){         #由指定的文件中读取测验数据
        English[++n] = $1               # 最后, n 将表示题目之题数
        Chinese[n] = $2
    }
    ARGV[1] = "-"                       # "-"表示由stdin(键盘输入)
    srand()                             # 以系统时间为随机数启始的种子
    question()                          #产生考题
}
 
{
    # awk自动读入由键盘上输入的数据(使用者回答的答案)
    if($1 != English[ind] )
        print "Try again!"
    else{
        print "\nYou are right !! Press Enter to Continue --- "
        getline
        question()
    }
}
function question(){
    ind = int(rand() * n) + 1           #以随机数选取考题
    system("clear")
    print "Press ""ctrl-d"" to exit"
    printf("\n%s ", Chinese[ind] " 的英文生字是: ")
}
' $*

注:awk的数学函数中提供两个与随机数有关的函数. srand( ) 以当前的系统时间作为随机数的种子. rand( ) 返回介于 0与1之间的(近似)随机数值.

9.编写递归程序

awk 中除了函数的参数列(Argument List)上的参数(Arguments),所有变量不管于何处出现,全被视为全局变量其生命持续至程序结束——该变量不论 function外或 function内皆可使用,只要变量名称相同所使用的就是同一个变量,直到程序结束. 此特性优劣参半, 最大的坏处是式中的变量不易被保护, 特别是递归调用本身, 执行子函数时会破坏父函数内的变量.

一个变通的方法是在函数的参数列中虚列一些参数函数执行中使用这些虚列的参数来记录不想被破坏的数据,如此执行子函数时就不会破坏到这些数据此外awk 并不会检查调用函数时所传递的参数个数是否一致$0, $1,.., NF, NR..也都是 global variable, 读者于递归函数中若有使用这些内建变量也应另外设立一些局部变量来保存,以免被破坏.

以下是一个常见的递归调用范例它要求使用者输入一串元素(各元素间用空白隔开然后印出这些元素所有可能的排列.

#!/bin/sh
awk '
BEGIN {
    print "请输入排列的元素,各元素间请用空白隔开"
    getline
    permutation($0, "")
    printf("\n共 %d 种排列方式\n", counter)
}
function permutation( main_lst, buffer,     new_main_lst, nf, i, j )
{
    $0 = main_lst           # 把main_lst指定给$0之后awk将自动进行字段分割.
    nf = NF                 # 故可用 NF 表示 main_lst 上存在的元素个数.
    
    # BASE CASE : 当main_lst只有一个元素时.
    if( nf == 1){
        #buffer的内容再加上main_lst就是完成一次排列的结果
        print buffer main_lst 
        counter++
        return
    }
    
    # General Case : 每次从 main_lst 中取出一个元素放到buffer中
    # 再用 main_lst 中剩下的元素 (new_main_lst) 往下进行排列
    else 
        for( i=1; i<=nf ;i++)
        {
            $0 = main_lst    # $0为全局变量已被破坏, 故重新把main_lst赋给$0,令awk再做一次字段分割
            new_main_lst = ""
            for(j=1; j<=nf; j++) # 连接 new_main_lst
                if( j != i ) 
                    new_main_lst = new_main_lst " " $j
            permutation( new_main_lst, buffer " " $i )
        }
}
' "$@"

注: awk 中欲将字符串concatenation(连接)直接将两字符串并置即可(Implicit Operator). awk使用者所编写的函数可再重用, 并不需要每个awk式中都重新编写. 将函数部分单独编写于一文件中, 当需要用到该函数时再以下列方式include进来

awk -f 函数文件名 -f awk主程序文件名 数据文件文件名

四、实战

1.去除空行

a

b

c
awk '{ if ($0 != "") rs=rs$0; } END { print rs }'
awk '
{
    if ($0 != ""){
        if (FNR != 1)
            rs=rs"\n";
        rs=rs$0;
    }   
}
END{ 
    print rs; 
}
'

整理自:http://kb.cnblogs.com/a/1310146/   http://man.lupaworld.com/content/manage/ringkee/awk.htm

Stay hungry Stay foolish
原文地址:https://www.cnblogs.com/xiangzi888/p/2440009.html