如何 select awk 中的日期范围
How to select date range in awk
我们正在制作一个实用程序,用于通过 ssh 连接到不同的服务器并收集所有错误日志并发送给相关团队,该实用程序将使用 awk 收集日志文件和过滤。例如
cat /app1/apache/tomcat7/logs/catalina.out | awk '[=11=]>=from&&[=11=]<=to' from="2019-02-01 12:00" to="2019-11-19 04:50"
我们正在数据库中保存上次加载的日期,并将此日期用作下一个 运行 的开始日期。
问题
awk 给定的日期范围似乎只适用于 yyyy-mm-dd HH:MM
日期格式。我们的日志文件有不同的日期格式。例如
EEE MMM dd yy HH:mm
EEE MMM dd HH:mm
yyyy-MM-dd hh:mm
dd MMM yyyy HH:mm:ss
dd MMM yyyy HH:mm:ss
问题
如何编写 awk 日期过滤器来处理日志文件中使用的任何日期格式?
我们无法在服务器上使用 perl/python。要求是为此仅使用 cat/awk/grep。
示例输入:
Sat Nov 02 13:07:48.005 2019 NA for id 536870914 in form Request
Tue Nov 05 13:07:48.009 2019 NA for id 536870914 in form Request
Sun Nov 10 16:29:22.122 2019 ERROR (1587): Unknown field ; at position 177 (category)
Mon Nov 11 16:29:22.125 2019 ERROR (1587): Unknown field ; at position 174 (category)
Tue Nov 12 07:59:48.751 2019 ERROR (1587): Unknown field ; at position 177 (category)
Thu Nov 14 10:07:41.792 2019 ERROR (1587): Unknown field ; at position 177 (category)
Sun Nov 17 08:45:22.210 2019 ERROR (1587): Unknown field ; at position 174 (category)
命令和过滤器:
cat error.log |awk '[=14=]>=from&&[=14=]<=to' from="Nov 16 10:58" to="Nov 19 04:50"
预期输出:
Sun Nov 17 08:45:22.210 2019 ERROR (1587): Unknown field ; at position 174 (category)
虽然从技术上讲您可以从 awk 调用 date
,但这种方法的帮助有限:
- 从
awk
调用日期(或其他程序)很昂贵(启动进程等)。如果日志文件很大,处理会很慢
- 看起来您正在寻找可以在远程服务器上执行的 'one-liner'。处理多种格式将需要不止一行。
考虑取消这些限制 - 以下一项(或多项):
- 将完整的日志文件传输到能够运行本地过滤器的机器,支持多个日期。
- 发送更复杂的脚本以在每个远程服务器上执行扫描。这将需要稍微多一些设置,但将消除通过 ssh 传输完整日志文件的需要。
- 自定义日志文件 - catalina、Apache 等,允许您控制日期格式。使它们全部产生
YYYY-MM-DD HH:MM
或类似的。
答案是 awk 不知道什么是日期。 Awk 知道数字和字符串,并且只能比较它们。所以当你想要 select 日期和时间时,你必须确保你比较的日期格式是 sortable 并且有很多格式:
| type | example | sortable |
|------------+---------------------------+----------|
| ISO-8601 | 2019-11-19T10:05:15 | string |
| RFC-2822 | Tue, 19 Nov 2019 10:05:15 | not |
| RFC-3339 | 2019-11-19 10:05:15 | string |
| Unix epoch | 1574157915 | numeric |
| AM/PM | 2019-11-19 10:05:15 am | not |
| MM/DD/YYYY | 11/19/2019 10:05:15 | not |
| DD/MM/YYYY | 19/11/2019 10:05:15 | not |
因此您必须将不可排序的格式转换为可排序的格式,主要使用字符串操作。可以实现您想要的功能的模板 awk 程序写在这里:
# function to convert a string into a sortable format
function convert_date(str) {
return sortable_date
}
# function to extract the date from the record
function extract_date(str) {
return extracted_date
}
# convert the range
(FNR==1) { t1 = convert_date(begin); t2 = convert_date(end) }
# extract the date from the record
{ date_string = extract_date([=11=]) }
# convert the date of the record
{ t = convert_date(date_string) }
# make the selection
(t1 <= t && t < t2) { print }
大多数时候,这个程序可以大大减少。如果以上内容存储在 extract_date_range.awk
中,您可以 运行 将其作为:
$ awk -f extract_date_range.awk begin="date-in-know-format" end="date-in-known-format" logfile
注意: 以上假定单行日志条目。稍作调整,您就可以处理多行日志条目。
在原题中,出现了以下格式:
EEE MMM dd yy HH:mm # not sortable
EEE MMM dd HH:mm # not sortable
yyyy-MM-dd hh:mm # sortable
dd MMM yyyy HH:mm:ss # not sortable
从上面可以看出,除第二种格式外,其他格式都可以轻松转换为可排序的格式。第二种格式错过了我们必须使用星期几进行详细检查的年份。这是非常困难的,而且永远不会 100% 防弹。
排除第二种格式,我们可以编写如下函数:
BEGIN {
datefmt1="^[a-Z][a-Z][a-Z] [a-Z][a-Z][a-Z] [0-9][0-9] [0-9][0-9] [0-9][0-9]:[0-9][0-9]"
datefmt3="^[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9] [0-9][0-9]:[0-9][0-9]"
datefmt4="^[0-9][0-9] [a-Z][a-Z][a-Z] [0-9][0-9][0-9][0-9] [0-9][0-9]:[0-9][0-9]:[0-9][0-9]"
}
# convert the range
(FNR==1) { t1 = convert_date(begin); t2 = convert_date(end) }
# extract the date from the record
{ date_string = extract_date([=14=]) }
# skip if date string is empty
(date_string == "") { next }
# convert the date of the record
{ t = convert_date(date_string) }
# make the selection
(t1 <= t && t < t2) { print }
# function to extract the date from the record
function extract_date(str, date_string) {
date_string=""
if (match(datefmt1,str)) { date_string=substr(str,RSTART,RLENGTH) }
else if (match(datefmt3,str)) { date_string=substr(str,RSTART,RLENGTH) }
else if (match(datefmt4,str)) { date_string=substr(str,RSTART,RLENGTH) }
return date_string
}
# function to convert a string into a sortable format
# converts it in the format YYYYMMDDhhmmss
function convert_date(str, a,fmt, YYYY,MM,DD,T, sortable_date) {
sortable_date=""
if (match(datefmt1,str)) {
split(str,a,"[ ]")
YYYY=(a[4] < 70 ? "19" : "20")a[4]
MM=get_month(a[2]); DD=a[3]
T=a[5]; gsub(/[^0-9]/,T)"00"
sortable_date = YYYY MM DD T
}
else if (match(datefmt3,str)) {
sortable_date = str"00"
gsub(/[^0-9]/,sortable_date)
}
else if (match(datefmt4,str)) {
split(str,a,"[ ]")
YYYY=a[3]
MM=get_month(a[2]); DD=a[1]
T=a[4]; gsub(/[^0-9]/,T)"00"
sortable_date = YYYY MM DD T
}
return sortable_date
}
# function to convert Jan->01, Feb->02, Mar->03 ... Dec->12
function get_month(str) {
return sprintf("%02d",(match("JanFebMarAprMayJunJulAugSepOctNovDec",str)+2)/3)
}
我们正在制作一个实用程序,用于通过 ssh 连接到不同的服务器并收集所有错误日志并发送给相关团队,该实用程序将使用 awk 收集日志文件和过滤。例如
cat /app1/apache/tomcat7/logs/catalina.out | awk '[=11=]>=from&&[=11=]<=to' from="2019-02-01 12:00" to="2019-11-19 04:50"
我们正在数据库中保存上次加载的日期,并将此日期用作下一个 运行 的开始日期。
问题
awk 给定的日期范围似乎只适用于 yyyy-mm-dd HH:MM
日期格式。我们的日志文件有不同的日期格式。例如
EEE MMM dd yy HH:mm
EEE MMM dd HH:mm
yyyy-MM-dd hh:mm
dd MMM yyyy HH:mm:ss
dd MMM yyyy HH:mm:ss
问题
如何编写 awk 日期过滤器来处理日志文件中使用的任何日期格式?
我们无法在服务器上使用 perl/python。要求是为此仅使用 cat/awk/grep。
示例输入:
Sat Nov 02 13:07:48.005 2019 NA for id 536870914 in form Request
Tue Nov 05 13:07:48.009 2019 NA for id 536870914 in form Request
Sun Nov 10 16:29:22.122 2019 ERROR (1587): Unknown field ; at position 177 (category)
Mon Nov 11 16:29:22.125 2019 ERROR (1587): Unknown field ; at position 174 (category)
Tue Nov 12 07:59:48.751 2019 ERROR (1587): Unknown field ; at position 177 (category)
Thu Nov 14 10:07:41.792 2019 ERROR (1587): Unknown field ; at position 177 (category)
Sun Nov 17 08:45:22.210 2019 ERROR (1587): Unknown field ; at position 174 (category)
命令和过滤器:
cat error.log |awk '[=14=]>=from&&[=14=]<=to' from="Nov 16 10:58" to="Nov 19 04:50"
预期输出:
Sun Nov 17 08:45:22.210 2019 ERROR (1587): Unknown field ; at position 174 (category)
虽然从技术上讲您可以从 awk 调用 date
,但这种方法的帮助有限:
- 从
awk
调用日期(或其他程序)很昂贵(启动进程等)。如果日志文件很大,处理会很慢 - 看起来您正在寻找可以在远程服务器上执行的 'one-liner'。处理多种格式将需要不止一行。
考虑取消这些限制 - 以下一项(或多项):
- 将完整的日志文件传输到能够运行本地过滤器的机器,支持多个日期。
- 发送更复杂的脚本以在每个远程服务器上执行扫描。这将需要稍微多一些设置,但将消除通过 ssh 传输完整日志文件的需要。
- 自定义日志文件 - catalina、Apache 等,允许您控制日期格式。使它们全部产生
YYYY-MM-DD HH:MM
或类似的。
答案是 awk 不知道什么是日期。 Awk 知道数字和字符串,并且只能比较它们。所以当你想要 select 日期和时间时,你必须确保你比较的日期格式是 sortable 并且有很多格式:
| type | example | sortable |
|------------+---------------------------+----------|
| ISO-8601 | 2019-11-19T10:05:15 | string |
| RFC-2822 | Tue, 19 Nov 2019 10:05:15 | not |
| RFC-3339 | 2019-11-19 10:05:15 | string |
| Unix epoch | 1574157915 | numeric |
| AM/PM | 2019-11-19 10:05:15 am | not |
| MM/DD/YYYY | 11/19/2019 10:05:15 | not |
| DD/MM/YYYY | 19/11/2019 10:05:15 | not |
因此您必须将不可排序的格式转换为可排序的格式,主要使用字符串操作。可以实现您想要的功能的模板 awk 程序写在这里:
# function to convert a string into a sortable format
function convert_date(str) {
return sortable_date
}
# function to extract the date from the record
function extract_date(str) {
return extracted_date
}
# convert the range
(FNR==1) { t1 = convert_date(begin); t2 = convert_date(end) }
# extract the date from the record
{ date_string = extract_date([=11=]) }
# convert the date of the record
{ t = convert_date(date_string) }
# make the selection
(t1 <= t && t < t2) { print }
大多数时候,这个程序可以大大减少。如果以上内容存储在 extract_date_range.awk
中,您可以 运行 将其作为:
$ awk -f extract_date_range.awk begin="date-in-know-format" end="date-in-known-format" logfile
注意: 以上假定单行日志条目。稍作调整,您就可以处理多行日志条目。
在原题中,出现了以下格式:
EEE MMM dd yy HH:mm # not sortable
EEE MMM dd HH:mm # not sortable
yyyy-MM-dd hh:mm # sortable
dd MMM yyyy HH:mm:ss # not sortable
从上面可以看出,除第二种格式外,其他格式都可以轻松转换为可排序的格式。第二种格式错过了我们必须使用星期几进行详细检查的年份。这是非常困难的,而且永远不会 100% 防弹。
排除第二种格式,我们可以编写如下函数:
BEGIN {
datefmt1="^[a-Z][a-Z][a-Z] [a-Z][a-Z][a-Z] [0-9][0-9] [0-9][0-9] [0-9][0-9]:[0-9][0-9]"
datefmt3="^[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9] [0-9][0-9]:[0-9][0-9]"
datefmt4="^[0-9][0-9] [a-Z][a-Z][a-Z] [0-9][0-9][0-9][0-9] [0-9][0-9]:[0-9][0-9]:[0-9][0-9]"
}
# convert the range
(FNR==1) { t1 = convert_date(begin); t2 = convert_date(end) }
# extract the date from the record
{ date_string = extract_date([=14=]) }
# skip if date string is empty
(date_string == "") { next }
# convert the date of the record
{ t = convert_date(date_string) }
# make the selection
(t1 <= t && t < t2) { print }
# function to extract the date from the record
function extract_date(str, date_string) {
date_string=""
if (match(datefmt1,str)) { date_string=substr(str,RSTART,RLENGTH) }
else if (match(datefmt3,str)) { date_string=substr(str,RSTART,RLENGTH) }
else if (match(datefmt4,str)) { date_string=substr(str,RSTART,RLENGTH) }
return date_string
}
# function to convert a string into a sortable format
# converts it in the format YYYYMMDDhhmmss
function convert_date(str, a,fmt, YYYY,MM,DD,T, sortable_date) {
sortable_date=""
if (match(datefmt1,str)) {
split(str,a,"[ ]")
YYYY=(a[4] < 70 ? "19" : "20")a[4]
MM=get_month(a[2]); DD=a[3]
T=a[5]; gsub(/[^0-9]/,T)"00"
sortable_date = YYYY MM DD T
}
else if (match(datefmt3,str)) {
sortable_date = str"00"
gsub(/[^0-9]/,sortable_date)
}
else if (match(datefmt4,str)) {
split(str,a,"[ ]")
YYYY=a[3]
MM=get_month(a[2]); DD=a[1]
T=a[4]; gsub(/[^0-9]/,T)"00"
sortable_date = YYYY MM DD T
}
return sortable_date
}
# function to convert Jan->01, Feb->02, Mar->03 ... Dec->12
function get_month(str) {
return sprintf("%02d",(match("JanFebMarAprMayJunJulAugSepOctNovDec",str)+2)/3)
}