# 04.4.4 正则匹配IPv4地址

一个**IPv4地址**被`.`号分成了四部分，每一部分都由8位二进制组成，十进制范围是0-255，即二进制的范围是`00000000`-`11111111`。

> *IPv6*的格式更加复杂，本节的代码不适用于IPv6。

本节的代码`findIPv4.go`将分为4个部分，第一部分：

> ```go
> package main
>
> import (
>    "bufio"
>    "fmt"
>    "io"
>    "net"
>    "os"
>    "path/filepath"
>    "regexp"
> )
>
> func findIp(input string) string {
>    partIp := "(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])"
>    grammer := partIp+"\\."+partIp+"\\."+partIp+"\\."+partIp
>    matchMe := regexp.MustCompile(grammer)
>    return matchMe.FindString(input)
> }
> ```

`findIPv4.go`的代码相对之前的复杂一些，所以需要使用的包比较多。

这部分代码定义的正则表达式，能够匹配IPv4地址，是整个程序的核心，如果你定义的正则表达式不正确，将永远找不到你想要的IPv4地址！

代码中的正则表达式稍后我会解释，在此之前必须了解IPv4地址的结构，比如IPv4的点分十进制表示方法以及每一部分都不能超过255，只有这样你能写出恰当的正则表达式。

正则表达式`partIp`定义了在一个IPv4地址中，每一部分可能出现的情况，比如可能是250-255的三位数，也可能是200-249，100-199，或者10-99两位数，0-9的个位数，以上的情况必须都考虑在内。

`grammer`变量定义了IPv4地址的四个部分，每一部分必须匹配`partIp`。

> `findIPv4.go`可以在任意文本文件中帮助你找到IPv4地址。

如果你有特殊需求，比如要搜索特定的IP地址，直接修改`findIPv4.go`的正则表达式即可。

第二部分：

> ```go
> func main() {
>    arguments := os.Args
>    if len(arguments) < 2 {
>       fmt.Printf("usage: %s logfile\n",filepath.Base(arguments[0]))
>       os.Exit(1)
>    }
>    for _, filename := range arguments[1:] {
>       f,err := os.Open(filename)
>       if err != nil {
>          fmt.Printf("error openning file %s\n",err)
>          os.Exit(-1)
>       }
>       defer f.Close()
> ```

首先通过`os.Args`获取搜索的文件，然后检查命令行参数的长度是否符合要求。接下来通过一个for\`循环迭代处理。

第四部分代码：

> ```go
> r := bufio.NewReader(f)
> for {
>    line,err := r.ReadString('\n')
>    if err == io.EOF {
>       break
>    } else  if err != nil {
>       fmt.Printf("error openning file %s\n",err)
>       break
>    }
> ```

类似于`selectColumn.go`的代码，我们使用`bufio.ReadString*()`逐行读取文本。

最后一部分：

> ```go
> ip := findIp(line)
>          trail := net.ParseIP(ip)
>          if trail.To4() == nil {
>             continue
>          } else {
>             fmt.Println(ip)
>          }
>       }
>    }
> }
> ```

对逐行读取的文本执行`findIp()`函数，`netParseIP()`会再次确保获取的是有效的IPv4地址。

执行`findIPv4.go`得到下面的输出：

> $ go run findIPv4.go auth.log
>
> 116.112.10.1 151.1.51.5 192.168.1.1

其实`findIPv4.go`会打印出很多重复的行（如果文件中有很多重复的IPv4地址）。我们可以配合UNIX命令对输出进一步处理，可以获得更加清晰直观的结果：

> go run findIPv4.go auth.log | sort -rn | uniq -c | sort -rn
>
> 38 191.168.1.1
>
> 22 182.12.5.1
>
> ....
>
> 9 10.18.2.64

`sort -rn`将`findIPv4.go`的输出作为输入，排序后倒序输出，`uniq -c`计算重复ip的出现次数，最后`sort -rn`按照重复ip的出现次数倒叙输出。

> 再次强调，`findIPv4.go`的核心是正则表达四的实现。如果正则表达式定义错误了，就会匹配不到你想要的数据，或者匹配到错误的数据。
