我有一个包含以下数据的文件(仅显示示例数据。该文件最多包含 2001 行)
0001:3002:2018/07/16:12.34.31:ERR
0002:3002:2018/07/16:12.34.44:ERR
0003:3002:2018/07/16:12.34.57:ERR
0004:3002:2018/07/16:12.35.10:ERR
0005:3002:2018/07/16:12.35.23:ERR
0006:3002:2018/07/16:12.35.36:ERR
0007:3002:2018/07/16:12.35.49:ERR
0008:3002:2018/07/16:12.36.02:ERR
0009:3002:2018/07/16:12.36.15:ERR
我将向 bash 脚本传递一个日期,例如 2018/07/16:12.36.15。我想读取该文件中的每一行,并将该行中的日期与传递的日期进行比较,并返回日期大于传递日期的行。
到目前为止我做了什么?
#!/bin/sh
SEARCH_DATE=$1
errorCodeFilePath=/home/.errorfile.log
lines=`cat $errorCodeFilePath`
for line in $lines; do
errorCodeDate=$(echo $line |grep -Eo '[[:digit:]]{4}/[[:digit:]]{2}/[[:digit:]]{2}:[[:digit:]]{2}.[[:digit:]]{2}.[[:digit:]]{2}');
if [ $errorCodeDate -ge $SEARCH_DATE ];
then
echo $errorCodeDate
fi
done
问题
我不确定日期比较是否有效?我收到“预期错误整数表达式”。我真的不知道如何编写 Bash 脚本,这是我的第一次尝试。
如何使这个日期比较有效?此外,在日期比较工作之后,我需要获取所有匹配行的第一个 : 和第二个 : 之间的数字。
答案1
您的脚本将整个文件读入变量,然后迭代该变量的值。这有三个问题:
- 在最一般的情况下,人们可能不知道输入文件的大小,这意味着在某些情况下,变量可能会变成非常大的。
- 循环变量的未加引号的值将依赖于 shell 在空格(空格、制表符和换行符)上分割数据。如果数据包含除换行符之外的任何空格,则循环可能会执行错误的操作。
- shell 将在循环之前对未加引号的变量的值执行文件名通配。这意味着如果数据包含通配模式,例如
*
或[...]
,那么这些模式将与现有文件名进行匹配。
这个答案使用了这样一个事实:所使用的时间戳是合理的,因为它们后面的时间戳在较早的时间戳之后排序(至少在 POSIX 语言环境中)。
#!/bin/bash
while IFS= read -r line; do
timestamp=${line%:*} # Remove ":ERR" at the end
timestamp=${timestamp#*:*:} # Remove numbers from start ("0001:3002:")
if [[ "$timestamp" > "$1" ]]; then
# According to the current locale, the timestamp in "$timestamp"
# sorts after the timestamp in "$1".
printf "Greater: %s\n" "$line"
fi
done <file
该脚本将采用与文件中格式相同的时间戳作为其唯一参数。它迭代文件的内容file
,并为每一行解析时间戳并将其与命令行上的时间戳进行比较。使用>
运算符 in进行比较,bash
如果文件中的时间戳(按字典顺序)排序在当前语言环境中的给定时间戳之后,则比较结果为 true。如果比较为真,则打印文件中的行。
通过删除行尾和行首的部分来解析行中时间戳的两个单独的替换可以替换为
timestamp=$( cut -d ':' -f 3,4 <<<"$line" )
但这会运行得更慢,因为它调用外部实用程序。
测试:
$ bash script.sh '2018/07/16:12.36.00'
Greater: 0008:3002:2018/07/16:12.36.02:ERR
Greater: 0009:3002:2018/07/16:12.36.15:ERR
如果您只想输出文件中的时间戳而不是原始行,请在命令中更改"$line"
为。"$timestamp"
printf
在这种情况下,您还可以通过执行如下循环来加快速度:
#!/bin/bash
cut -d ':' -f 3,4 file |
while IFS= read -r timestamp; do
if [[ "$timestamp" > "$1" ]]; then
# According to the current locale, the timestamp in "$timestamp"
# sorts after the timestamp in "$1".
printf "Greater: %s\n" "$timestamp"
fi
done
在这里,我们用来从文件中cut
获取第三和第四:
分隔列(时间戳),这意味着我们不必对原始行进行任何解析。
有关的:
答案2
您的想法是正确的,但您可以修复一些问题以使脚本按预期工作。
- 首先使用
cat
文件并存储在变量中并循环最多是一种反模式。该方法会通过空格来中断字符串。请改用带 while 循环的文件重定向。 - 始终引用 shell 变量以保留变量内容并防止发生上一点中提到的分词
- 而是
grep
使用 的本机正则表达式支持来bash
提取日期字符串以进行 EPOCH 转换 - 默认情况下
bash
不提供比较date
字符串的方法,需要转换为等效的 EPOCH 值并进行整数比较
因此,无需使用任何第三方工具,仅使用 shell 内部结构,即可将其组合在一起。需要date
GNU utils 的命令才能使用该-d
标志,并且可能不是date
在 *BSD 机器的本机上工作。
#!/usr/bin/env bash
errorCodeFilePath="/home/.errorfile.log"
re='[0-9]+/[0-9]+/[0-9]+:[0-9]+\.[0-9]+\.[0-9]+'
convDateString() {
day="${1##*:}"
time="${1%%:*}"
printf '%d' "$(date -d"$time ${day//./:}" +%s)"
}
while IFS= read -r line; do
inputArg="$1"
inputEPOCH="$(convDateString "${inputArg}")"
if [[ $line =~ $re ]]; then
lineEPOCH="$(convDateString "${BASH_REMATCH[*]}")"
if [ "$lineEPOCH" -gt "$inputEPOCH" ]; then
echo "${BASH_REMATCH[@]}" is greater
fi
fi
done<"$errorCodeFilePath"
在有问题的示例输入上测试您的文件,如下所示
$ bash script.sh "2018/07/16:12.36.00"
2018/07/16:12.36.02 is greater
2018/07/16:12.36.15 is greater
综上所述,您应该考虑阅读为什么使用 shell 循环处理文本被认为是不好的做法?。因为与其他专用于文件处理的工具相比,使用 shell 进行文本处理速度较慢。
答案3
尝试这个,
#!/bin/sh
SEARCH_DATE="$1"
errorCodeFilePath=/home/nagios/temp/test1
lines=`cat $errorCodeFilePath`
for line in $lines; do
errorCodeDate=$(echo $line |grep -Eo '[[:digit:]]{4}/[[:digit:]]{2}/[[:digit:]]{2}:[[:digit:]]{2}.[[:digit:]]{2}.[[:digit:]]{2}');
if [ $(date -d "`echo $errorCodeDate| tr ':' ' '| tr '.' ':'`" +%s) -ge $(date -d "`echo $SEARCH_DATE| tr ':' ' '| tr '.' ':'`" +%s) ];
then
echo $errorCodeDate
fi
done
答案4
如果您想迭代行,则for
需要将其设置IFS
为换行符。当 while 循环时,这会稍微快一些。
#!/bin/bash
IFS=$'\n'
for a in $(<file.txt); do
[[ $1:ERR < ${a#*:*:} ]] && echo "$a"
done
$ ./script.sh 2018/07/16:12.35.10
(awk版本)
#!/usr/bin/awk -bf
BEGIN { FS=OFS=":" } {
if (d < $3 FS $4) { print $0 }
}
$ ./script.awk -vd=2018/07/16:12.35.10 file.txt
如果您已经知道存在一个日期并且只想打印其余行,您可以按日期、时间对文件进行排序,并使用它grep -A
来获取匹配行之后的上下文。tail +2
将允许输出从第二行开始,从而有效地从输出中删除匹配的行。
$ grep < <(sort -t : -k 3,4 < file.txt) \
-A2000 -Fe '2018/07/16:12.35.10' | tail +2 | sort -n