用于过滤数据流中的字符串并输出到屏幕的 Shell 脚本

用于过滤数据流中的字符串并输出到屏幕的 Shell 脚本

我试图使跟踪日志文件并仅将单个字段的输出打印到屏幕上变得容易。

我的 Linux 机器上的日志文件如下所示:

2022-10-21 16:00:08;areq_in=0;areq_qavg=0;areq_qmax=0;areq_sent=0;ares_out=0;ares_out_err=0;ares_out_ok=0;ares_qavg=0;ares_qmax=0;ares
_recv=0;ares_tavg=0;ares_tmax=0;creq_out=0;creq_qavg=0;creq_qmax=0;creq_recv=0;cres_in=0;cres_qavg=0;cres_qmax=0;cres_sent=0;lic_rej=0
;lic_use=0;mbr_from=0;mbr_to=0;mreject=0;mreject_conn=0;msys_recv=0;msys_sent=0;

日志中的每一行都由;- 分隔的字段组成,其中大部分采用“key=value”的形式。

所以我想做的只是打印creq_recv屏幕上“键”名称的字段。

我从某人的修复中获取了这个脚本,但我认为我遗漏了一些东西。

#!/bin/bash
creq_recv=$1
my_variable=`tail -f stats_2022102116.log | awk -F: -v creq_recv="$1" '$1 == creq_recv {$1=$1; print}' stats_2022102116.log`
echo "$my_variable"

因为输出为空

[root@priti ]$ ./chk_conn_log.sh
    
[root@priti ]$ 

无论值是什么,输出都应该打印在屏幕上creq_recv。例如,像下面这样

creq_recv=12
creq_recv=34
creq_recv=65

答案1

您可以使用仅打印行的匹配部分和/或PCRE 或扩展正则表达式的选项grep来执行您想要的操作,这让我们可以使用“一个或多个非;字符”:-o-P-E[^;]+

tail -f stats_2022102116.log | grep -oP '(?<=^|;)creq_recv=[^;]+'

或者,更安全的是,我们可以有其他以 结尾的字段名称creq_recv

tail -f stats_2022102116.log | grep -oP '(^|;)\Kcreq_recv=[^;]+'

awk失败了,因为-v creq_recv="$1"意味着“将 awk 变量的值设置creq_recv为传递给脚本的第一个参数的值”。但由于您启动的脚本没有参数 ( ./chk_conn_log.sh),$1所以它是空的,所以creq_recv它也是空的。

无论如何,即使设置了变量,这也不会起作用。因为脚本awk是错误的,而且tail -f除非您停止它,否则它永远不会退出,所以它echo不会被执行。您想要的是这样的:

#!/bin/bash

tail -f stats_2022102116.log | 
    awk -F';'  '{ for(i=1; i<=NF; i++){ if ($i ~ /^creq_recv=/) { print $i } } }'

echo,只有tail更正后的 awk 脚本。不过只要使用grep上面的命令就可以了,简单多了。

答案2

您尝试的解决方案包含几个问题,这些问题会导致您遇到不良行为。

  1. 您的脚本需要一个命令行参数($1在“bash”级别),您在调用它时似乎没有指定./chk_conn_log.sh
  2. 您的脚本将该(未指定=空)值复制到多变的creq_recv。所以,creq_recv 就你的外壳而言, 是一个空变量。
  3. 你想要过滤正在进行的通过 输出进程的输出tail,但将结果分配给稍后用 打印的 shell 变量echo。这是行不通的,因为只有当两个进程都终止时变量才会被填充。但是,如果不再写入日志文件,tail则只会等待新的输入 - 所以它永远不会终止,并且您的脚本实际上被困在那里,但是,
  4. tail尽管您想要通过管道传输to的输出awk,但您还明确地将日志文件声明为参数,因此awk将简单地运行文件的内容就像现在调用脚本一样,其中可能尚未包含任何行,然后终止。所以你的变量my_variable是空的,你的脚本的输出也是空的。

即使是这样,

  1. 您说(示例显示)日志文件是;分隔的,但您指示awk使用:分隔符。
  2. 你设置了一个awk 变量 creq_recv到(未指定的)命令行参数,并在awk程序内部检查是否第一的 :- 分隔字段等于的内容awk多变的creq_recv。但是,该变量为空(见上文),并且“字段”将包含字符串2022-10-21 16(直到第一个的日期/时间:),因此由于多种原因,条件永远不会满足,这意味着即使日志文件已经调用脚本时包含行,输出仍为空。
  3. 您将打印整行,而不仅仅是显示 的字段creq_recv=xx

也就是说,什么我想你想实现可以按如下方式完成:

tail -f stats_2022102116.log | awk -F';' '{for (i=1;i<=NF;i++) {if (index($i,"creq_recv=")==1) print $i}}'
  • 这会将字段分隔符设置为;
  • 然后,对于每个(传入)输入行,它将迭代所有此类字段,如果该字段以 string 开头creq_recv=,则打印该字段。

请注意,我选择使用文字字符串比较而不是正则表达式匹配,因为(在一般情况下)它允许您查找包含正则表达式中具有特殊含义的字符的字符串,而无需转义它们。如果您需要正则表达式匹配(根据您的帖子,这里不是这种情况),您可以更改

if (index($i,"creq_recv=")==1)

if ($i ~ /your regular expression/)

答案3

使用(以前称为 Perl_6)

#recover all key/value pairs:

~$ cat file | raku -e 'my @a = lines>>.split(";", :skip-empty)>>.[1..*].flat;  \
                       my %hash;  for @a>>.split("=") {%hash.=append: $_};  \
                      .say for %hash.sort;'  
#recover values for specific key:

~$ cat file | raku -e 'my @a = lines>>.split(";", :skip-empty)>>.[1..*].flat; \
                       my %hash;  for @a>>.split("=") {%hash.=append: $_}; \
                       say %hash<creq_recv>;' 

上面是用 Raku(一种 Perl 系列编程语言)编码的答案。这个问题实际上需要一个键值解决方案,而 Raku 并没有让人失望。

对于上面的两个代码示例,除了最后一条语句之外,它们是相同的。简而言之,lines读入文件的文件,默认情况下具有截断尾随换行符的效果\n。然后,此输入按分号拆分;以生成[1..*]索引的元素,以删除第一个(时间/日期)元素、flat进行维护并存储在@a数组中。然后@aare的元素>>=等号单独分割。这些结果元素被append编辑全体%hash.当以这种方式给定输入时,Raku 从每连续的两个元素中生成一个键/值对。

在第一个代码示例中,返回所有键/值对。在第二个代码示例中,仅creq_recv返回与键关联的值。

输入示例:

2022-10-21 16:00:08;areq_in=0;areq_qavg=0;areq_qmax=0;areq_sent=0;ares_out=0;ares_out_err=0;ares_out_ok=0;ares_qavg=0;ares_qmax=0;ares_recv=0;ares_tavg=0;ares_tmax=0;creq_out=0;creq_qavg=0;creq_qmax=0;creq_recv=0;cres_in=0;cres_qavg=0;cres_qmax=0;cres_sent=0;lic_rej=0;lic_use=0;mbr_from=0;mbr_to=0;mreject=0;mreject_conn=0;msys_recv=0;msys_sent=0;

第一个代码示例的示例输出(第二个代码示例返回0):

areq_in => 0
areq_qavg => 0
areq_qmax => 0
areq_sent => 0
ares_out => 0
ares_out_err => 0
ares_out_ok => 0
ares_qavg => 0
ares_qmax => 0
ares_recv => 0
ares_tavg => 0
ares_tmax => 0
creq_out => 0
creq_qavg => 0
creq_qmax => 0
creq_recv => 0
cres_in => 0
cres_qavg => 0
cres_qmax => 0
cres_sent => 0
lic_rej => 0
lic_use => 0
mbr_from => 0
mbr_to => 0
mreject => 0
mreject_conn => 0
msys_recv => 0
msys_sent => 0

要获得更丰富的返回信息,请使用 Raku 的=>配对构造函数,将最后一条语句更改为:

$_ = "creq_recv" andthen say $_ => %hash{$_};

...返回:

creq_recv => 0

最后,Raku 实现了react块,这些块可以设计为接收Supply(例如,$*IN.lines通过 StdIN 接收到的行)和whenever接收到的运行代码。就像这样:

~$ tail -f test.log | raku -e 'react {whenever Supply( $*IN.lines ) {   \
                               my @a = $_.map( *.split(";", :skip-empty).[1..*] ).flat;  \
                               my %hash; for @a.map: *.split("=") { %hash.=append: $_ };  \
                               $_ = "creq_recv" andthen say $_ => %hash{$_} // Nil; } };'
creq_recv => 0
creq_recv => 0
^C

https://docs.raku.org/language/concurrency#react
https://docs.raku.org/language/hashmap
https://docs.raku.org/routine/=%3E
https://raku.org

PS:brian d foy 的书中有一个关于“关联”(即哈希和映射)的绝对精彩的章节学习 Perl6

相关内容