skyscribe.programming.thinking

汇小流以成江海,积跬步以至千里

Awk高级特性

| Comments

UNIX环境下,用shell做一些常见的文本处理工作是很方便高效的事情;虽然目前有很多自带丰富类库的脚本语言可以完成同样的事情,但是对于一些特殊的文本格式处理任务,传统的sed/awk/grep组合还是有很明显的优势:没有复杂的版本问题和类库部署依赖问题,能够快速解决问题。awk作为一门DSL,自身也带有对很多高级特性(相对于shell本身)的支持,灵活应用往往能收到奇效。

array类型

awk自身支持类似于C语言数组的数据结构,称之为array,但是其下标却不仅仅限于数字,可以是字符串等其它类型;行为上来说更似于一个关联容器,从某个变量关联到另外一个变量:

1
2
3
4
5
awk 'BEGIN{
    arr['aa'] = 1
    arr[4] = 2
    print arr['aa']
}'

基本的array操作:

  • 遍历元素
1
2
3
4
5
6
7
8
awk 'BEGIN{
    arr[1] = "cc"
    arr[2] = "ddd"
    arr['ccd'] = "demo"
    for (i in arr){
        print arr[i]
    }
}'
  • 多维数组 – 多个下表操作内部会被转换为SUBSEP连接的字符串为索引的array:
array.awk (awk_array.awk) download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#!/usr/bin/awk -f
BEGIN{
    for(i = 0; i < 10; i++){
        for(j = 0; j < 8; j++){
            grid[i, j] = i * j;
        }
    }
    grid[1,2,7] = 14;
    grid[4,2,7] = 56;

    if ( (1,2) in grid){
        printf("grid[1][2]=%d\n", grid[1,2]);
    }

    for(idx in grid){
        fmt = "grid[";
        n = split(idx, indices, SUBSEP);
        for (sub_idx = 1; sub_idx <= n; sub_idx++){
            fmt = fmt indices[sub_idx] "]["
        }
        printf("%s = %d\n", substr(fmt, 1, length(fmt) - 1), grid[idx])
    }
}

需要获取具体下标的时候,可以使用split函数,传入SUBSEP作为分隔符,依次取得每一个下标。用于数据统计的时候,这一技巧相当顺手好用。

字符串操作和正则表达式技巧

字符串操作和C语言非常类似,可以使用:

  • substr 取得子串,和很多脚本语言类似,传入一个源字串,初始下标和长度,例如:
1
2
3
4
5
awk 'BEGIN{
    teststr = "this is a test string..."
    printf("<%s>\n", substr(teststr, 1, length(teststr) - 1));
    printf("<%s>\n", substr(teststr, 0, length(teststr) - 1));
}'

这里的第二个printf传入的下标从0开始,那么实际上得到的字串从末尾处被截去了2个字符。

  • length可以取得字符串长度信息

正则表达式regexp

基本的类sed类操作正如其名称所示(部分操作可以省略源字符串,默认等同于和$0相匹配)

  • sub 用于正则替换左数起第一个匹配
1
2
echo "The lazy dog" | awk '{sub(/[ey] /, "lagggg> ");print}'
#Thlagggg> lazy dog
  • gsub 用于全局替换,所有满足条件的部分都被替换
1
2
echo "The lazy dog" | awk '{gsub(/[ey] /, "lagggg> ");print}'
Thlagggg> lazlagggg> dog
  • gensub 是一个更通用形式的正则替换操作,它保持源字符串不动,将修改后的串返回
1
2
3
echo "The lazy dog" | gawk '{new = gensub(/[ey] /, "lagggg> ", "g");print; print new}'
#The lazy dog
#Thlagggg> lazlagggg> dog
  • match 可用于模式匹配并返回匹结果的开始下标,并设置RSTART为匹配到的下标,RLENGTH为匹配字串的长度
1
2
3
4
5
6
7
echo "The lazy dog jump over the brown fox" | awk '{
result = match($0, /(dog)(.*)(fox)/)
print result, RSTART, RLENGTH
print substr($0, RSTART, RLENGTH)
> }'
#10 10 27
#dog jump over the brown fox

脚本内嵌和独立awk文件

一般可以将不太长的awk脚本处理放入管道操作中,让后用'来包含实际的awk处理脚本。当awk脚本过长的时候或者为了便于维护,也可以用一个独立的脚本来存放awk脚本内容,但是其shebang部分应该设置为: #!/usr/bin/awk -f, 这里的 -f表示后边的内容是awk脚本文件。

Comments