首页 > 解决方案 > Bash 使用正则表达式将 API 调用的结果添加到日志文件

问题描述

我有这种格式的原始数据:

[Wed Sep 01 15:37:10.709437 2021] [authz_core:error] [pid 15732] [client [IP REDACTED]:56163] AH01630: client denied by server configuration: /var/www/[REDACTED].com/html/wp-admin/admin-post.php

API 调用如下所示:

curl 'https://api.ipgeolocation.io/ipgeo?apiKey=[REDACTED]&ip=[REDACTED]'

结果是:

{"ip":"[REDACTED]","continent_code":"EU","continent_name":"Europe","country_code2":"DE","country_code3":"DEU","country_name":"Germany","country_capital":"Berlin","state_prov":"Bayern","district":"Oberbayern","city":"München","zipcode":"81549","latitude":"[REDACTED]","longitude":"[REDACTED]","is_eu":true,"calling_code":"+49","country_tld":".de","languages":"de","country_flag":"https://ipgeolocation.io/static/flags/de_64.png","geoname_id":"8772423","isp":"[REDACTED]","connection_type":"","organization":"[REDACTED]","currency":{"code":"EUR","name":"Euro","symbol":"€"},"time_zone":{"name":"Europe/Berlin","offset":1,"current_time":"2021-09-02 23:08:55.514+0200","current_time_unix":1630616935.514,"is_dst":true,"dst_savings":1}}

到目前为止我得到了什么:

grep -P "\b(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?):[0-9]+\b" ~/Documents/hosting/web/logscript_results.txt

这匹配任何 IPv4 地址和端口号。我希望将端口替换为相应地址的 country_name,例如:

[Wed Sep 01 15:37:10.709437 2021] [authz_core:error] [pid 15732] [client [IP REDACTED]: Germany AH01630: client denied by server configuration: /var/www/[REDACTED].com/html/wp-admin/admin-post.php

如何从我的正则表达式继续前进?

标签: regexbash

解决方案


正如评论中提到的,如果您要在 bash 中使用 JSON 并且能够在系统上安装软件包,那么您绝对应该获取并使用jq.

首先,让我们grep获取 IP 地址的原始数据/日志文件,并使用命令替换将其存储在变量中。在这里,我正在使用您的正则表达式,但添加了-o仅捕获 IP+端口的选项:

LOG_IP_PORT=$(grep -Po "\b(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?):[0-9]+\b" ~/Documents/hosting/web/logscript_results.txt)

现在,我假设 ipgeolocation API 只需要 IP 地址,而不需要端口。即使它可以接受端口,将 IP 地址仅存储在另一个变量中也会对我们有用。所以我们将使用变量替换来修剪字符串:

LOG_IP_ONLY=${LOG_IP_PORT/%:*}

接下来,是时候使用 curl 进行 API 调用了,并使用我们的$LOG_IP_ONLY变量代替 IP 地址查询字段。同样,我们将结果存储在带有命令替换的变量中:

JSON_RESPONSE=$(curl "https://api.ipgeolocation.io/ipgeo?apiKey=[REDACTED]&ip=${LOG_IP_ONLY}")
# must use double-quotes ("), NOT single-quotes (')

下面是jq解析 API 响应的地方:

COUNTRY_NAME=$(jq -r '.country_name' <<< $JSON_RESPONSE)
# the -r option outputs only raw data without any quotation

但是,如果您必须使用grep/regex 而不是jq,请划掉最后一行并使用:

COUNTRY_NAME=$(grep -Po '"country_name"\s*:\s*"\K([^"]*)' <<< $JSON_RESPONSE)

现在我们有了所有必要的数据,我们只需要在您的日志文件中进行实际替换:

sed -i "s/${LOG_IP_PORT}/${LOG_IP_ONLY}:${COUNTRY_NAME}/g" ~/Documents/hosting/web/logscript_results.txt
# the -i option edits the input file in place

把它们放在一起:

#!/bin/bash

LOG_IP_PORT=$(grep -Po "\b(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?):[0-9]+\b" ~/Documents/hosting/web/logscript_results.txt)
LOG_IP_ONLY=${LOG_IP_PORT/%:*}

JSON_RESPONSE=$(curl "https://api.ipgeolocation.io/ipgeo?apiKey=[REDACTED]&ip=${LOG_IP_ONLY}")

COUNTRY_NAME=$(jq -r '.country_name' <<< $JSON_RESPONSE)
# or:
# COUNTRY_NAME=$(grep -Po '"country_name"\s*:\s*"\K([^"]*)' <<< $JSON_RESPONSE)

sed -i "s/${LOG_IP_PORT}/${LOG_IP_ONLY}:${COUNTRY_NAME}/g" ~/Documents/hosting/web/logscript_results.txt

需要注意的是,只有在您的日志文件中只有一个 IP 地址时,这才会按原样工作。

如果您只想将此应用于您可以使用的日志文件中的第一个 IP 地址head,如下所示:

LOG_IP_PORT=$(grep -Po "\b(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?):[0-9]+\b" ~/Documents/hosting/web/logscript_results.txt | head -n 1)

如果您只想将此应用于日志文件中的最后一个 IP 地址,请使用tail

LOG_IP_PORT=$(grep -Po "\b(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?):[0-9]+\b" ~/Documents/hosting/web/logscript_results.txt | tail -n 1)

如果要将此脚本应用于日志文件中的所有 IP 地址,则需要定义 IP 地址数组并使用for.


推荐阅读