如何grep上一个匹配行之上的一行

时间:2018-01-03 15:35:41

标签: regex linux bash grep

我有日志文件,其中只定期附加日期。我的日志文件如下所示:

Monday 2017
foo foo foo foo foo foo foo foo foo foo foo foo 
foo foo foo ALARM foo foo foo foo foo foo foo foo 
foo foo foo foo foo foo foo foo foo foo foo foo 
foo foo foo foo foo foo ALARM foo foo foo foo foo
foo foo foo foo foo foo foo foo foo foo foo foo

我正在编写一个类似这样的脚本:

grep 'ALARM' myfile.log | tail -1

我需要搜索上一个警报上方的上一个日期条目,并将其包含在我的结果中。我不知道匹配的警报线上面会有多少行。

期望的输出:

Monday 2017
foo foo foo foo foo foo ALARM foo foo foo foo foo

6 个答案:

答案 0 :(得分:2)

假设日期模式为Monday 2017

grep -E 'Monday 2017|ALARM' | grep -B1 'ALARM'

第二个grep是删除ALARM匹配之间的多个日期模式,

编辑:再次阅读问题似乎只有匹配ALARM的最后一行是通缉的,我会跟随perl一个班轮:

perl -ne 'if(/Monday 2017/){$last_date=$_}if(/ALARM/){$date=$last_date;$line=$_}END{print $date,$line}' <<END
Monday 2017
foo foo foo foo foo foo foo foo foo foo foo foo 
foo foo foo ALARM foo foo foo foo foo foo foo foo 
foo foo foo foo foo foo foo foo foo foo foo foo 
foo foo foo foo foo foo ALARM foo foo foo foo foo
foo foo foo foo foo foo foo foo foo foo foo foo
END

答案 1 :(得分:1)

您可以使用tac逐行反转流(请参阅seq 10 | tac查看其功能)。这是不便宜的,请注意,但如果你的东西足够小,这可以提供一个简单的解决方案:

grep -B 9999999 lastSearchTerm my.log | tac | grep -B 9999999 firstSearchTerm | tac

这会将块从firstSearchTerm打印到lastSearchTerm。

grep -B 9999999 lastSearchTerm my.log | tac | tail -n +2 | grep -m 1 lastBeforeTerm

这将只打印lastSearchTerm之前包含lastBeforeTerm的最后一行。

对于您的具体情况,应该这样做:

grep -B 9999999 ALARM my.log | tac | {
  IFS= read -e line
  grep -m 1 '2017'
  echo "$line"
}

(调整2017部分以匹配任何看起来像时间戳的行。)

当然,这不是最快的解决方案,但它很简单,适用于小输入。

答案 2 :(得分:1)

class SongDetail extends Component{ render(){ const { song, loading } = this.props.data; console.log( "song details are...", this.props, "---", song ); if( loading ) return <div>loading...</div>; // loading = false when done loading and ready return( <div> <Link to = "/">Back</Link> <h3>{ song.title }</h3> <LyricList lyrics = { song.lyrics }/> <LyricCreate songId = { this.props.params.id }/> </div> ); } } + Awk 解决方案:

示例tac内容:

myfile.log

工作:

some text text text
Sunday 2017
foo foo foo foo foo foo foo foo foo foo foo foo 
foo foo foo ALARM foo foo foo foo foo foo foo foo 
foo foo foo foo foo foo foo foo foo foo foo foo 
bar foo foo foo foo foo ALARM foo foo foo foo foo
bar foo foo foo foo foo foo foo foo foo foo foo
Monday 2017
foo foo foo foo foo foo foo foo foo foo foo foo 
foo foo foo ALARM foo foo foo foo foo foo foo foo 
foo foo foo foo foo foo foo foo foo foo foo foo 
foo foo foo foo foo foo ALARM foo foo foo foo foo
text foo foo foo foo foo foo foo foo foo foo foo
  • awk '/ALARM/{ f=1 }f && /^[A-Z][a-z]+ 2[0-9]{3}/{ print; exit }' <(tac myfile.log) - 反向打印文件行
  • tac myfile.log - 遇到/ALARM/{ f=1 }行时 - 使用标记ALARM
  • 设置处理的起始阶段
  • f - 指示“date”
  • 的模式
  • /^[A-Z][a-z]+ 2[0-9]{3}/ - 打印当前行(作为结果行)并立即终止脚本执行

输出:

print; exit

答案 3 :(得分:0)

这假设“日期”的特征是包含day和四位数的行:

tac myfile.log \
    | sed -En '/ALARM/,/day [[:digit:]]{4}/{/day [[:digit:]]{4}/{p;q}}'

与其他解决方案一样,它使用tac反向打印行;然后sed命令执行此操作:

-n默认禁止输出。

/ALARM/,/day [[:digit:]]{4}/ { # In the range from ALARM to the date
    /day [[:digit:]]{4}/{      # On the line of the date
        p                      # Print just that line
        q                      # Exit
    }
}

q是在我们找到我们想要的内容之后避免阅读文件的其余部分。

请注意,某些seds可能需要额外的分号,如{p;q;}

答案 4 :(得分:0)

awk解决方案,

awk 'NF==2 {d=$0}; /ALARM/ { printf("%s\n%s\n", d, $0)}' sample.txt

输出:

Monday 2017
foo foo foo ALARM foo foo foo foo foo foo foo foo 
Monday 2017
foo foo foo foo foo foo ALARM foo foo foo foo foo

答案 5 :(得分:0)

我们不能用Grep有效地做到这一点。这是一个简单的Sed构造要记住:

sed -n '/before/ {h;n;}; /after/ {x;p;x;p;}' < input.txt

这将存储与模式before匹配的最新行,然后在遇到与模式after匹配的后续行时将其打印出来。然后,它也打印出匹配after的行。要打破它:

  • -n标志会抑制每一行的输出 - 我们会告诉Sed输出我们想要的内容。
  • /before/ - 当我们找到与模式before匹配的行时...
    • h - 将其保存到保留空间缓冲区以供日后使用。
    • n - 继续下一行。
  • /after/ - 当我们找到与模式after匹配的行时...
    • x;p - 使用保留缓冲区(before)的内容交换该行并打印出来。
    • x;p - 将after退回保留缓冲区并打印出来。

这种运行速度非常快,因为我们可以在一次传递中过滤输入,而无需先输出管道或反转文件。

现在,让我们将其应用于问题中的示例:

sed -n '/^date pattern$/ {h;n;}; /ALARM/ {x;p;x;p;}' < input.txt

这只是将特定模式插入我上面描述的Sed程序中 - 它每次看到ALARM时都会输出最近看到的日期和匹配的行。由于该问题只想在每个日期之后显示包含ALARM last 行,因此我们需要稍微修改该程序:

sed -n '
    /^date pattern$/ {
        :alarm
        x
        /ALARM/ {s/^\(date pattern\)\n.*\n\(.*ALARM.*\)$/\1\n\2/;p;n;}
    }
    /ALARM/ H
    $ b alarm
' < input.txt

而不是只保留日期行,这会缓冲日期包含ALARM的每一行,直到Sed遇到下一个日期,之后它将打印日期和保持缓冲区中的最后ALARM行。我们检查是否存在ALARM,因此我们不会在没有发生警报时打印日期。 :alarm声明一个分支标签,我们可以使用b alarm返回到文件的最后一行(用$表示)来处理保留空间缓冲区中剩余的任何内容。 / p>

我在每个示例中都使用[A-Z][a-z]\+day [0-9]\{4\} date pattern,但会根据需要进行调整。

编辑:我想我误解了这个问题。看起来我们只想要整个文件中的最后一个日期和最后一个警报线。如果是这样,使用Tac首先反转文件会更快,但会消耗更多内存:

tac input.txt | sed -n '/ALARM/ {h;:a;n;/^date pattern$/ {p;x;p;q;}; ba;}'

使用这种方法,我们将最后一个警报存储在文件中,并在找到并打印文件中的最后一个日期后打印它。我们在找到最后一个日期后立即使用q退出,以避免处理其余日期。如果我们的系统上没有Tac,我们也可以使用Sed来反转文件:

sed '1!G;h;$!d' < input.txt | sed ...