## 入门

### 试试

该程序可用于选择文件中的特定记录并对其执行操作

    $ awk -F: '{print $1, $NF}' /etc/passwd

- | - | -
--- | --- | ---
| `-F:` | 冒号作为分隔符
| `{...}` | awk 程序
| `print` | 打印当前记录
| `$1` | 第一个字段
| `$NF` | 最后一个字段
| `/etc/passwd` | 输入数据文件

### Awk 程序

    BEGIN          {<初始化>}
       <pattern 1> {<计划动作>}
       <pattern 2> {<计划动作>}
       ...
    END            {< 最后的动作 >}

#### 示例

    awk '
        BEGIN { print "\n>>>Start" }
        !/(login|shutdown)/ { print NR, $0 }
        END { print "<<<END\n" }
    ' /etc/passwd

### 变量

              $1      $2/$(NF-1)    $3/$NF
               ▼          ▼           ▼
            ┌──────┬──────────────┬───────┐
    $0/NR ▶ │  ID  │  WEBSITE     │  URI  │
            ├──────┼──────────────┼───────┤
    $0/NR ▶ │  1   │  baidu.com   │  awk  │
            ├──────┼──────────────┼───────┤
    $0/NR ▶ │  2   │  google.com  │  25   │
            └──────┴──────────────┴───────┘

    # 第一个和最后一个字段
    awk -F: '{print $1,$NF}' /etc/passwd
    # 带行号
    awk -F: '{print NR, $0}' /etc/passwd
    # 倒数第二个字段
    awk -F: '{print $(NF-1)}' /etc/passwd
    # 自定义字符串
    awk -F: '{print $1 "=" $6}' /etc/passwd

查看: Awk 变量

### Awk 程序示例

    awk 'BEGIN {print "hello world"}'        # 打印 "hello world"
    awk -F: '{print $1}' /etc/passwd         # -F: 指定字段分隔符
    # /pattern/ 仅对匹配的模式执行操作
    awk -F: '/root/ {print $1}' /etc/passwd
    # BEGIN 块在开始时执行一次
    awk -F: 'BEGIN { print "uid"} { print $1 }' /etc/passwd
    # END 块在最后执行一次
    awk -F: '{print $1} END { print "-done-"}' /etc/passwd

### 条件

    awk -F: '$3>30 {print $1}' /etc/passwd

查看: Conditions 条件

### 生成 1000 个空格

    awk 'BEGIN{
        while (a++ < 1000)
            s=s " ";
        print s
    }'

查看: Loops

### 数组 Arrays

    awk 'BEGIN {
       fruits["mango"] = "yellow";
       fruits["orange"] = "orange"
       for(fruit in fruits) {
         print fruit " 的颜色是 " fruits[fruit]
       }
    }'

查看: Awk 数组

### 函数 Functions

    # => 5
    awk 'BEGIN{print length("hello")}'
    # => HELLO
    awk 'BEGIN{print toupper("hello")}'
    # => hel
    awk 'BEGIN{print substr("hello", 1, 3)}'

查看: Functions

## Awk 变量

### 内置变量

:- | :-
--- | ---
`$0` | 全线
`$1, $2...$NF` | 第一个，第二个……最后一个字段
`NR` | 记录总数(`N`umber of `R`ecords)
`NF` | N个字段(`N`number of `F`ields)
`OFS` | `O`utput `F`ield `S`eparator 输出字段分隔符 (default " ")
`FS` | input `F`ield `S`eparator 输入字段分隔符 (default " ")
`ORS` | `O`utput `R`ecord `S`eparator 输出记录分隔符 (default "\n")
`RS` | input `R`ecord `S`eparator 输入记录分隔符 (default "\n")
`FILENAME` | 文件名

### 表达式

:- | :-
--- | ---
`$1 == "root"` | 第一个字段等于根
`{print $(NF-1)}` | 倒数第二个字段
`NR!=1{print $0}` | 从第 2 条记录开始
`NR > 3` | 从第 4 条记录开始
`NR == 1` | 第一次记录
`END{print NR}` | 总记录
`BEGIN{print OFMT}` | 输出格式
`{print NR, $0}` | 行号
`{print NR " " $0}` | 行号(选项卡)
`{$1 = NR; print}` | 用行号替换第一个字段
`$NF > 4` | 最后一个字段 > 4
`NR % 2 == 0` | 甚至记录
`NR==10, NR==20` | 记录 10 到 20
`BEGIN{print ARGC}` | 总 `arguments`
`ORS=NR%5?",":"\n"` | 连接记录

### 示例

打印总和和平均值

    awk -F: '{sum += $3}
         END { print sum, sum/NR }
    ' /etc/passwd

打印参数

    awk 'BEGIN {
        for (i = 1; i < ARGC; i++)
            print ARGV[i] }' a b c

输出字段分隔符为逗号

    awk 'BEGIN { FS=":";OFS=","}
        {print $1,$2,$3,$4}' /etc/passwd

匹配位置

    awk 'BEGIN {
        if (match("One Two Three", "Tw"))
            print RSTART }'

匹配时长

    awk 'BEGIN {
        if (match("One Two Three", "re"))
            print RLENGTH }'

### 仅限 GNU awk

:- | :-
--- | ---
`ENVIRON` | 环境变量
`IGNORECASE` | 忽略大小写
`CONVFMT` | 转换格式
`ERRNO` | 系统错误
`FIELDWIDTHS` | 固定宽度字段

### 环境变量

:- | :-
--- | ---
`ARGC` | 数字或参数
`ARGV` | 参数数组
`FNR` | 文件记录数(`F`ile `N`umber of `R`ecords)
`OFMT` | 数字格式 (default "%.6g")
`RSTART` | 字符串中的位置
`RLENGTH` | 比赛时长
`SUBSEP` | 多维数组分隔符 (default "\034")
`ARGIND` | 参数索引

### 定义变量

    awk -v var1="Hello" -v var2="Wold" '
        END {print var1, var2}
    ' </dev/null

#### 使用 shell 变量

    awk -v varName="$PWD" '
        END {print varName}' </dev/null

## Awk 运算符

### 运算符

:- | :-
--- | ---
`{print $1}` | 第一个字段
`$2 == "foo"` | 等于
`$2 != "foo"` | 不等于
`"foo" in array` | 在数组中

#### 正则表达式

:- | :-
--- | ---
`/regex/` | 行匹配
`!/regex/` | 行不匹配
`$1 ~ /regex/` | 字段匹配
`$1 !~ /regex/` | 字段不匹配

#### 更多条件

:- | :-
--- | ---
`($2 <= 4 || $3 < 20)` | 或者
`($1 == 4 && $3 < 20)` | 和

### 运算符

#### 算术运算

- `+`
- `-`
- `*`
- `/`
- `%`
- `++`
- `--`

#### 速记作业

- `+=`
- `-=`
- `*=`
- `/=`
- `%=`

#### 比较运算符

- `==`
- `!=`
- `<`
- `>`
- `<=`
- `>=`

### 示例

    awk 'BEGIN {
        if ("foo" ~ "^fo+$")
            print "Fooey!";
    }'

#### 不匹配

    awk 'BEGIN {
        if ("boo" !~ "^fo+$")
            print "Boo!";
    }'

#### 如果在数组中

    awk 'BEGIN {
        assoc["foo"] = "bar";
        assoc["bar"] = "baz";
        if ("foo" in assoc)
            print "Fooey!";
    }'

## Awk 函数

### 常用功能

函数 | 描述
--- | ---
`index(s,t)` | 字符串 `s` 中出现字符串 `t` 的位置，如果未找到则为 `0`
`length(s)` | 字符串 `s` 的长度(如果没有 `arg`，则为 `$0`)
`rand` | `0` 到 `1` 之间的随机数
`substr(s,index,len)` | 返回从索引开始的 `s` 的 `len-char` 子字符串(从 `1` 开始计数)
`srand` | 为 `rand` 设置种子并返回之前的种子
`int(x)` | 将 `x` 截断为整数值
`split(s,a,fs)` | 将字符串 `s` 拆分为数组 `a` 由 `fs` 拆分，返回 `a` 的长度
`match(s,r)` | 字符串 `s` 中出现正则表达式 `r` 的位置，如果未找到，则为 `0`
`sub(r,t,s)` | 将 `t` 替换为字符串 `s` 中第一次出现的正则表达式 `r`(如果未给出 `s`，则替换为 `$0`)
`gsub(r,t,s)` | 用 `t` 替换字符串 `s` 中所有出现的正则表达式 `r`
`system(cmd)` | 执行cmd并返回退出状态
`tolower(s)` | 字符串 `s` 转小写
`toupper(s)` | 字符串 `s` 转大写
`getline` | 将 `$0` 设置为当前输入文件中的下一个输入记录

### 用户定义函数

    awk '
        # 返回最小数量
        function find_min(num1, num2){
           if (num1 < num2)
           return num1
           return num2
        }
        # 返回最大数量
        function find_max(num1, num2){
           if (num1 > num2)
           return num1
           return num2
        }
        # 主功能
        function main(num1, num2){
           result = find_min(num1, num2)
           print "Minimum =", result

           result = find_max(num1, num2)
           print "Maximum =", result
        }
        # 脚本执行从这里开始
        BEGIN {
           main(10, 60)
        }
    '

## Awk 数组

### 带索引的数组

    awk 'BEGIN {
        arr[0] = "foo";
        arr[1] = "bar";
        print(arr[0]); # => foo
        delete arr[0];
        print(arr[0]); # => ""
    }'

### 带键的数组

    awk 'BEGIN {
        assoc["foo"] = "bar";
        assoc["bar"] = "baz";
        print("baz" in assoc); # => 0
        print("foo" in assoc); # => 1
    }'

### 带拆分的数组

    awk 'BEGIN {
        split("foo:bar:baz", arr, ":");
        for (key in arr)
            print arr[key];
    }'

### 带有排序的数组

    awk 'BEGIN {
        arr[0] = 3
        arr[1] = 2
        arr[2] = 4
        n = asort(arr)
        for (i = 1; i <= n ; i++)
            print(arr[i])
    }'

### 多维

    awk 'BEGIN {
        multidim[0,0] = "foo";
        multidim[0,1] = "bar";
        multidim[1,0] = "baz";
        multidim[1,1] = "boo";
    }'

### 多维迭代

    awk 'BEGIN {
        array[1,2]=3;
        array[2,3]=5;
        for (comb in array) {
            split(comb,sep,SUBSEP);
            print sep[1], sep[2],
            array[sep[1],sep[2]]
        }
    }'

## Awk 条件

### if-else 语句

    awk -v count=2 'BEGIN {
        if (count == 1)
            print "Yes";
        else
            print "Huh?";
    }'

#### 三元运算符

    awk -v count=2 'BEGIN {
        print (count==1) ? "Yes" : "Huh?";
    }'

### 存在

    awk 'BEGIN {
        assoc["foo"] = "bar";
        assoc["bar"] = "baz";
        if ("foo" in assoc)
            print "Fooey!";
    }'

#### 不存在

    awk 'BEGIN {
        assoc["foo"] = "bar";
        assoc["bar"] = "baz";
        if ("Huh" in assoc == 0 )
            print "Huh!";
    }'

### switch

    awk -F: '{
        switch (NR * 2 + 1) {
            case 3:
            case "11":
                print NR - 1
                break
            case /2[[:digit:]]+/:
                print NR
            default:
                print NR + 1
            case -1:
                print NR * -1
        }
    }' /etc/passwd

## Awk 循环

### for...i

    awk 'BEGIN {
        for (i = 0; i < 10; i++)
            print "i=" i;
    }'

#### 1 到 100 之间的 2 的幂

    awk 'BEGIN {
        for (i = 1; i <= 100; i *= 2)
            print i
    }'

### for...in

    awk 'BEGIN {
        assoc["key1"] = "val1"
        assoc["key2"] = "val2"
        for (key in assoc)
            print assoc[key];
    }'

#### Arguments

    awk 'BEGIN {
        for (argnum in ARGV)
            print ARGV[argnum];
    }' a b c

### 示例

#### 反向记录

    awk -F: '{ x[NR] = $0 }
        END {
            for (i = NR; i > 0; i--)
            print x[i]
        }
    ' /etc/passwd

#### 反向字段

    awk -F: '{
        for (i = NF; i > 0; i--)
            printf("%s ",$i);
        print ""
    }' /etc/passwd

#### 按记录求和

    awk -F: '{
        s=0;
        for (i = 1; i <= NF; i++)
            s += $i;
        print s
    }' /etc/passwd

#### 总结整个文件

    awk -F: '
        {for (i = 1; i <= NF; i++)
            s += $i;
        };
        END{print s}
    ' /etc/passwd

### while

    awk 'BEGIN {
        while (a < 10) {
            print "- " " concatenation: " a
            a++;
        }
    }'

#### do...while

    awk '{
        i = 1
        do {
            print $0
            i++
        } while (i <= 5)
    }' /etc/passwd

### Break

    awk 'BEGIN {
        break_num = 5
        for (i = 0; i < 10; i++) {
            print i
            if (i == break_num)
                break
        }
    }'

### Continue

    awk 'BEGIN {
        for (x = 0; x <= 10; x++) {
            if (x == 5 || x == 6)
                continue
            printf "%d ", x
        }
        print ""
    }'

## Awk 格式化打印

### 用法

#### 右对齐

    awk 'BEGIN{printf "|%10s|\n", "hello"}'
    # |     hello|

#### 左对齐

    awk 'BEGIN{printf "|%-10s|\n", "hello"}'
    # |hello     |

### 通用说明符

特征符 | 描述
--- | ---
`c` | ASCII 字符
`d` | 十进制整数
`e`, `E`, `f` | 浮点格式
`o` | 无符号八进制值
`s` | 细绳
`%` | 文字百分比

### Space 空间

    awk -F: '{
        printf "%-10s %s\n", $1, $(NF-1)
    }' /etc/passwd | head -n 3

输出

    root       /root
    bin        /bin
    daemon     /sbin

### Header 标题头

    awk -F: 'BEGIN {
        printf "%-10s %s\n", "User", "Home"
        printf "%-10s %s\n", "----","----"}
        { printf "%-10s %s\n", $1, $(NF-1) }
    ' /etc/passwd | head -n 5

输出

    User       Home
    ----       ----
    root       /root
    bin        /bin
    daemon     /sbin

## 各种各样的

### 正则表达式元字符

- `\`
- `^`
- `$`
- `.`
- `[`
- `]`
- `|`
- `(`
- `)`
- `*`
- `+`
- `?`

### 转义序列

:- | :-
--- | ---
`\b` | 退格
`\f` | 换页
`\n` | 换行(换行)
`\r` | 回车
`\t` | 水平选项卡
`\v` | 垂直选项卡

### 运行脚本

    $ cat demo.awk
    #!/usr/bin/awk -f
    BEGIN { x = 23 }
          { x += 2 }
    END   { print x }
    $ awk -f demo.awk /etc/passwd
    69

## 另见

- GNU awk 用户指南 (www-zeuthen.desy.de)
- AWK cheatsheet (gist.github.com)