使用 sed 将日志输出中的 IP 地址替换为主机名

使用 sed 将日志输出中的 IP 地址替换为主机名

我正在尝试用主机名替换 dnsmasq 日志文件中的 IP 地址。日志文件正在控制台上使用命令“tail -f /var/log/dnsmasq.log”进行“监视”,我想将输出通过管道传输到 sed 中,以仅在包含以下内容的行上将 IP 地址替换为主机名文本“查询”。 IP 地址始终位于这些行的末尾。

示例行是:

Apr  1 00:47:43 dnsmasq[1004]: query[A] gs-loc.apple.com from 10.1.1.188

我相信该命令的形式如下:

tail -f /var/log/dnsmasq.log | sed -e "s/'regex'/$(dig +short -x $1)/g"

“正则表达式”需要识别包含字符串“query”的行,从该行末尾提取 IP 地址并将其(以某种方式)存储在变量中 - 我$1在这里使用了符号 - 在替换表达式中使用挖。

更新:我没有提及 IP 地址始终采用 10.1.nn 的形式

答案1

不幸的是,sed 无法运行外部命令,同时还传递从其输入获取的参数。

这是一个适合您的 Bash 脚本解决方案:

tail -f dnsmasq.log | { while IFS= read -r line ; do { [[ "${line}" =~ ": query[A]" ]] && printf '%s %s\n' "${line% *} " $(dig +short -x "${line##* }"); } || echo "${line}"; done ; }

分解解释:(仅出于清晰目的,复制和粘贴时可能不起作用

tail -f dnsmasq.log | \
    { \
        while IFS= read -r line ; do \           # for each line read in from tail ...
            if [[ "${line}" =~ ": query[A]" ]] ; # if it has the literal string ': query[A]'
            then \
                printf '%s %s\n' "${line% *} " \ # print it (purged of last field, which is the IP address) ...
                $(dig +short -x "${line##* }") \ # along with dig's output
            else \                               # otherwise ...
                echo "${line}" \                 # just print it all as it is
            fi \
        done ; \
    }

答案2

这有点有效(但使用“awk”而不是“sed”):

$ echo $'Apr  1 00:47:43 dnsmasq[1004]: query[A] gs-loc.apple.com from 8.8.8.8' | awk '/query/{ IP=$NF; $NF=""; L=$0; "host " IP | getline name; $0=name; print L,$NF }'
Apr 1 00:47:43 dnsmasq[1004]: query[A] gs-loc.apple.com from  google-public-dns-a.google.com.

...需要一些改进,例如,如果主机查找失败;也许正则表达式“查询”需要更具体一些。

下面是 awk 命令的解释:

/询问/{ ... }在与正则表达式“查询”匹配的行上执行 {...} (仅打印其他行)

IP=$NF将新变量“IP”设置为该行最后一个字段的值(IP 地址)

$NF=“”zap 线上的最后一个字段

L=$0将新变量“L”设置为剩余行(即没有 IP 地址)

“主机”IP |获取线路名称在 IP 地址上运行“host”并将结果放入新变量“name”中

$0=姓名将当前行设置为“host”命令的输出,以便我们可以在下一个命令中使用 $NF。

打印 L,$NF打印“L”(不带 IP 地址的输入行)和“host”命令的最后一个字段(主机名)。

答案3

为每个 IP 地址运行dig效率非常低,并且会增加 DNS 服务器的负载。我会perl在这里使用:

perl -MSocket -pe 's{(?<![\d.])\d+\.\d+\.\d+\.\d+(?![\d.])}{
    $ip = inet_aton($&);
    $cache{$ip} //= gethostbyaddr($ip,AF_INET) // "UNKNOWN[$&]"
  }ge'

这正在查询您系统的名称服务,因此可能是DNS、mDNS、LDAP、NIS+...或系统上/etc/hosts为主机名解析或等效配置的任何内容,可能会通过名称服务缓存服务,例如或,我们正在还实施缓存以避免多次查询同一 IP 地址。/etc/nsswitch.confnscdsssd

我们只匹配 4 个.分隔的十进制数字序列,而不是其他 IPv4 地址格式,但请注意,对于inet_aton(),前导 0 会导致数字被视为八进制,因此010.010.010.010实际上8.8.8.8(与大多数以 IP 地址作为参数的事物相同,但不是dig -x)。

如果您需要它只像这样查询 DNS 服务器dig,您可以Net::DNS使用gethostbyaddr()

perl -MNet::DNS -pe '
  sub resolve {
    my ($r) = rr($_[0]);
    if (defined($r)) {
      return $r->ptrdname;
    } else {
      return "UNKNOWN[$_[0]]";
    }
  }
  s{(?<![\d.])\d+\.\d+\.\d+\.\d+(?![\d.])}{$cache{$&} //= resolve $&}ge'

相关内容