順序に関係なく、行から複数​​のパターンを抽出します

Aug 20 2020

私は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を使用して1行でそれを取得する方法を理解しようとしています(つまり、X行を1にマージすることではありません)

編集2:入力のパラメーターの順序を混同

編集3:申し訳ありませんが、これについては前に説明する必要がありました-perl作業しているマシンで制限されているようです。StephaneとSundeepが提供する回答は、ローカルでテストすると完全に機能しますが、最終的に実行するために必要なマシンでは機能しません。awk、grep、sedが主にサポートされているオプションのようです:(

回答

8 Sundeep Aug 20 2020 at 13:06

With 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/

7 StéphaneChazelas Aug 20 2020 at 13:15

perl使用すると、次のようなアプローチを使用できます。

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

キーが属性名で値がname=valuesであるハッシュテーブルを作成し、後で必要なものを出力します。

交換する$&$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+))'

(3つの属性すべてが存在する必要があります)。

sed

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

これらの最後の2つは、属性が行の最初の単語ではないことを前提としています(これは、サンプルを考えると妥当な前提のようです)。

1 LL3 Aug 20 2020 at 22:21

EDIT 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目的の出力順序に関して、式の順序が逆になっていることに注意してください。番号付けもスクリプトに埋め込まれ、前述のように出力行番号を出力し、必要なパラメーターのいずれかが実際に行に存在する場合にのみ行を出力します。AFAIKがBSDに知られていないアトムをsed 使用しているので、GNU構文を利用していることにも注意してください。POSIX準拠の同等のものが可能かもしれませんが、さらに拡張される可能性があります。\dsed

ただし、これはすでにひどく長く、出力するパラメーターが増えるとますます複雑になるため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 後にスクリプト自体への引数として、出力する正確なパラメーターとその出現順序を指定し--ます。このスクリプトも、必要なパラメーターの少なくとも1つが実際に行に存在する場合にのみ、行を出力します。

1 EdMorton Aug 21 2020 at 20:27

入力にname = valueのペアがあるときはいつでも、最初にそのマッピング(f[])を保持する配列を作成するのが最善だと思います。次に、名前で値にアクセスできます。例:

$ awk -F'[ =]' '{ for (i=2;i<NF;i+=2) f[$i]=$i"="$(i+1)
    print f["port"], f["authenticate"], f["appID"]
}' file
port=1234 authenticate=true appID=dummyAppId1
port=1244 authenticate=false appID=dummyAppId2
port=1235 authenticate=true appID=dummyAppId3
Sumak Aug 20 2020 at 14:39

同様の問題を抱えている他のユーザーを助けることができる場合は、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