从行中提取多个模式,无论顺序如何

从行中提取多个模式,无论顺序如何

我是 Unix 脚本编写的新手,所以请耐心等待。

我得到一个文件,其中包含每行进程的信息。我需要从每一行中提取有关这些进程的某些信息。

文件示例 -

process1 port=1234 appID=dummyAppId1 authenticate=true <some more params>
process3 port=1244 authenticate=false appID=dummyAppId2 <some more params>
process2 appID=dummyAppId3 port=1235 authenticate=true <some more params>

所需的输出是 -

1
port=1234 authenticate=true appID=dummyAppId1 
2
port=1244 authenticate=false appID=dummyAppId2
3
port=1235 authenticate=true appID=dummyAppId3

每行的数字 1、2、3 仅表示输出文件的行号。

我已经尝试使用该sed s/命令,但它是特定于顺序的,而输入文件中的参数不遵循顺序 - 因此,输入文件中的某些行被跳过。

这是我的命令 -

sed -nr 'appId/s/(\w+).*port=([^ ]+) .*authenticate=[^ ]+) .*appId=[^ ]+) .*/\2\t\3\t\4/p' | sed =

谁能指导我如何提取这些参数(无论顺序如何)?

谢谢!

编辑1:我设法以这种方式使用 grep 的后视零宽度断言功能 -

grep -Po '(?<=pattern1=)[^ ,]+|(?<=pattern2=)[^ ,]+|(?<=pattern3=)[^ ,]+|(?<=pattern4=)[^ ,]+' filename

但这似乎给出了新行中每一行的输出,即

1234
true
dummyAppId1

试图弄清楚如何使用 grep 将其放在一行上(即不是通过将 X 行合并为 1 行)

编辑2:混淆输入中参数的顺序

编辑 3:抱歉,我应该早点提到这一点 -perl似乎在我工作的机器上受到限制。虽然当我在本地测试它时,Stephane 和 Sundeep 提供的答案完美地工作,但它在我最终需要它运行的机器上不起作用。看起来 awk、grep 和 sed 是主要支持的选项:(

答案1

使用awk(已测试GNU awk,不确定是否适用于其他实现)

$ cat kv.awk
/appID/ {
    for (i = 1; i <= NF; i++) {
        $i ~ /^port=/ && (a = $i)
        $i ~ /^authenticate=/ && (b = $i)
        $i ~ /^appID=/ && (c = $i)
    }
    print NR "\n" a, b, c
}

$ awk -v OFS='\t' -f kv.awk ip.txt
1
port=1234   authenticate=true   appID=dummyAppId1
2
port=1244   authenticate=false  appID=dummyAppId2
3
port=1235   authenticate=true   appID=dummyAppId3


perl

$ # note that the order is changed for second line here
$ cat ip.txt
process1 port=1234 authenticate=true appID=dummyAppId1 <some more params>
process3 port=1244 appID=dummyAppId2 authenticate=false <some more params>
process2 port=1235 authenticate=true appID=dummyAppId3 <some more params>

$ perl -lpe 's/(?=.*(port=[^ ]+))(?=.*(authenticate=[^ ]+))(?=.*(appID=[^ ]+)).*/$1\t$2\t$3/; print $.' ip.txt 
1
port=1234   authenticate=true   appID=dummyAppId1
2
port=1244   authenticate=false  appID=dummyAppId2
3
port=1235   authenticate=true   appID=dummyAppId3
  • (?=.*(port=[^ ]+))第一个捕获组port
  • (?=.*(authenticate=[^ ]+))第二个捕获组,authenticate依此类推
  • print $.对于行号
  • 为了避免部分匹配,如果单词边界足够,请使用 等\bport\bappID否则,使用(?<!\S)(port=[^ ]+)基于空格的限制。

如果您需要仅打印包含appID或任何其他此类条件的行,请更改-lpe-lne并更改print $.print "$.\n$_" if /appID/

答案2

对于perl,您可以使用如下方法:

perl -lne 'my %h;
           $h{$1} = $& while /(\S+?)=(\S+)/g;
           print "@h{qw(port authenticate appID)}"'

您可以在其中构建一个哈希表,其键是属性名称,值是 s name=value,然后打印您想要的内容。

如果您只想输出值,请替换$&为。$2

awk与:相同

awk '
  {
    split("", h)
    for (i = 1; i <= NF; i++)
      if (n = index($i, "=")) h[substr($i, 1, n - 1)] = $i
    print h["port"], h["authenticate"], h["appID"]
  }'

使用pcregrep,您可以执行以下操作:

pcregrep -o1 -o2 -o3 --om-separator=' ' '(?x)
  ^(?=.*?\s(port=\S+))
   (?=.*?\s(authenticate=\S+))
   (?=.*?\s(appID=\S+))'

(这需要所有三个属性都存在)。

sed

sed 'G
     s/[[:space:]]\(port=[^[:space:]]*\).*\n.*/&\1/
     s/[[:space:]]\(authenticate=[^[:space:]]*\).*\n.*/& \1/
     s/[[:space:]]\(appID=[^[:space:]]*\).*\n.*/& \1/
     s/.*\n//'

最后两个假设属性不是该行的第一个单词(鉴于您的样本,这似乎是一个合理的假设)。

答案3

根据您的编辑 3,我认为sed如果您为每个参数创建一个s///表达式,如下所示,您仍然可以这样做:

sed -nE 's/^(.*)(appID=[^[:blank:]]+\s)(.*)$/\2\t\1\3/
         s/^(.*)(authenticate=[^[:blank:]]+\s)(.*)$/\2\t\1\3/
         s/^(.*)(port=[^[:blank:]]+\s)(.*)$/\2\t\1\3/
         T;=
         s/^(([^[:blank:]]+\s+){,3}).*/\1/
         p'

s请注意表达式相对于所需输出顺序的相反顺序。编号也嵌入在脚本中,如您所提到的打印输出行号,并且仅当任何一个所需参数实际存在于一行中时,它才会打印一行。另请注意,我正在利用 GNUsed 语法,因为您一直在使用\dBSD 不知道的原子sed。符合 POSIX 的等效项可能是可能的,但可能会更加扩展。

然而,这已经非常长,并且随着输出参数的增加会变得越来越复杂,因此awk像下面这样的脚本可能更通用:

awk '
    BEGIN {ac=ARGC; ARGC=0; OFS="\t"}
    {
        str=$0; NF=0
        for (i=1; i<ac; i++)
            if (match(str, ARGV[i]"=[^[:blank:]]*"))
                $(NF+1)=substr(str, RSTART, RLENGTH)
    }
    NF {print ++nr; print}
    ' -- port authenticate appID

您可以指定要输出的确切参数及其出现顺序,awk 作为--.仅当一行中实际存在至少一个所需参数时,该脚本才会打印一行。

答案4

如果它可以帮助其他用户解决类似问题,请使用 Ruby 提出(详细)建议:

# passing the log file as parameter
lines = File.open(ARGV[0]).read.split("\n")

lines.each_with_index do |line, i|
  words  = line.split(' ')
  output = []

  puts i + 1
  output << words.select { |w| w =~ /port=\d+/ }
  output << words.select { |w| w =~ /authenticate=\w+/ }
  output << words.select { |w| w =~ /appID=\w+/ }

  puts output.join(' ')
end

相关内容