แยกหลายรูปแบบจากบรรทัดโดยไม่คำนึงถึงลำดับ

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 (เช่นไม่ผ่านการรวมเส้น X เป็น 1)

แก้ไข 2: ผสมลำดับของพารามิเตอร์ในอินพุต

แก้ไข 3: ฉันขอโทษฉันควรจะพูดถึงเรื่องนี้ก่อนหน้านี้ - perlดูเหมือนว่าจะถูก จำกัด ในเครื่องที่ฉันใช้งาน แม้ว่าคำตอบของ Stephane และ Sundeep จะทำงานได้อย่างสมบูรณ์แบบเมื่อฉันทดสอบในเครื่อง แต่ก็ไม่สามารถใช้งานได้กับเครื่องที่ฉันต้องการเพื่อให้ทำงานได้ในที่สุด ดูเหมือนว่า awk, grep และ sed จะเป็นตัวเลือกที่รองรับเป็นหลัก :(

คำตอบ

8 Sundeep Aug 20 2020 at 13:06

ด้วย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+))'

(อันนั้นต้องมีคุณสมบัติทั้งสามอย่าง)

ด้วยsed:

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

สองคำสุดท้ายนี้ถือว่าแอตทริบิวต์ไม่ใช่คำแรกของบรรทัด (ซึ่งดูเหมือนเป็นการสันนิษฐานที่สมเหตุสมผลสำหรับตัวอย่างของคุณ)

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นิพจน์ตามลำดับเอาต์พุตที่ต้องการ การกำหนดหมายเลขยังฝังอยู่ในสคริปต์การพิมพ์หมายเลขบรรทัดเอาต์พุตตามที่คุณกล่าวถึงและจะพิมพ์บรรทัดต่อเมื่อพารามิเตอร์ที่ต้องการมีอยู่ในบรรทัด ยังทราบว่าฉันใช้ประโยชน์จาก GNU sed ไวยากรณ์ที่คุณได้ใช้\dอะตอมซึ่ง AFAIK จะไม่เป็นที่รู้จัก sedBSD อาจเป็นไปได้ที่เทียบเท่ากับ 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 สคริปต์เองหลังจากไฟล์--. สคริปต์นี้จะพิมพ์บรรทัดก็ต่อเมื่อมีพารามิเตอร์ที่ต้องการอย่างน้อยหนึ่งพารามิเตอร์อยู่ในบรรทัดเท่านั้น

1 EdMorton Aug 21 2020 at 20:27

เมื่อใดก็ตามที่มีคู่ชื่อ = ค่าในอินพุตฉันคิดว่าดีที่สุดในการสร้างอาร์เรย์ที่เก็บการแม็พนั้น ( 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

หากสามารถช่วยผู้ใช้รายอื่นที่มีปัญหาคล้ายกันได้ข้อเสนอ (verbose) โดยใช้ 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