Estrai più modelli dalla linea indipendentemente dall'ordine

Aug 20 2020

Sono nuovo nello scripting Unix, quindi per favore abbi pazienza.

Mi viene fornito un file che contiene informazioni sui processi su ciascuna riga. Ho bisogno di estrarre determinate informazioni su questi processi da ogni riga.

Esempio del file -

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>

L'output desiderato è -

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

I numeri 1, 2 e 3 su ogni riga indicano semplicemente il numero di riga del file di output.

Ho già provato a utilizzare il sed s/comando ma è specifico per l'ordine, mentre i parametri nel file di input non seguono un ordine - di conseguenza, alcune righe nel file di input vengono saltate.

Ecco il mio comando -

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

Qualcuno potrebbe guidarmi su come estrarre quei parametri indipendentemente dall'ordine?

Grazie!

Modifica 1: sono riuscito a utilizzare la funzione di asserzione a larghezza zero look-behind di grep in questo modo -

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

ma questo sembra dare l'output per ogni riga in nuove righe, ad es

1234
true
dummyAppId1

Cercando di capire come ottenerlo su una riga usando grep (cioè non tramite l'unione di X linee in 1)

Modifica 2: confondere l'ordine dei parametri nell'input

Modifica 3: mi dispiace, avrei dovuto menzionarlo prima - perlsembra essere limitato alle macchine su cui lavoro. Mentre le risposte fornite da Stephane e Sundeep funzionano perfettamente quando lo provo localmente, non funzionerebbe sulle macchine su cui ho bisogno per funzionare finalmente. Sembra che awk, grep e sed siano le opzioni principalmente supportate :(

Risposte

8 Sundeep Aug 20 2020 at 13:06

Con awk(testato con GNU awk, non so se funziona con altre implementazioni)

$ 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


Con 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=[^ ]+)) primo gruppo di acquisizione per port
  • (?=.*(authenticate=[^ ]+))secondo gruppo di acquisizione per authenticatee così via
  • print $. per il numero di riga
  • Per evitare corrispondenze parziali, utilizzare \bport, \bappIDecc. Se il confine di parola è sufficiente. In caso contrario, utilizzare (?<!\S)(port=[^ ]+)per limitare in base agli spazi.

Se è necessario stampare solo righe contenenti appIDo qualsiasi altra condizione simile, passare -lpeae -lnepassare print $.aprint "$.\n$_" if /appID/

7 StéphaneChazelas Aug 20 2020 at 13:15

Con perl, potresti usare un approccio come:

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

Dove costruisci una tabella hash le cui chiavi sono i nomi degli attributi ei valori sono name=valuei se stampa quelli che vuoi in seguito.

Sostituisci $&con $2se desideri solo i valori in uscita.

Lo stesso con 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"]
  }'

Con pcregreppuoi fare:

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

(quello richiede che tutti e tre gli attributi siano presenti).

Con sed:

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

Gli ultimi due presumono che gli attributi non siano la prima parola della riga (il che sembra un presupposto ragionevole dato il tuo esempio).

1 LL3 Aug 20 2020 at 22:21

Rispettando il tuo EDIT 3, penso che potresti ancora farlo sedse hai fatto s///un'espressione per ogni parametro come questa:

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'

Notare l'ordine inverso delle sespressioni rispetto all'ordine di output desiderato. La numerazione è anche incorporata nello script, stampa i numeri di riga di output come hai menzionato e stampa una riga solo se uno qualsiasi dei parametri desiderati è effettivamente presente in una riga. Nota anche che sto sfruttando la sed sintassi GNU poiché hai utilizzato \datomi che, per quanto ne so, BSD non conosce sed. Un equivalente conforme a POSIX potrebbe essere possibile ma sarebbe probabilmente più esteso.

Tuttavia, è già orribilmente lungo e diventerebbe sempre più complesso all'aumentare dei parametri da produrre, quindi uno awkscript come il seguente potrebbe essere più versatile:

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

Dovresti specificare i parametri esatti che desideri visualizzare e il loro ordine di visualizzazione, come argomenti dello awk script stesso dopo il file --. Anche questo script stampa una riga solo quando almeno uno dei parametri desiderati è effettivamente presente in una riga.

1 EdMorton Aug 21 2020 at 20:27

Ogni volta che ci sono coppie nome = valore nell'input, trovo che sia meglio creare prima un array che contenga quella mappatura ( f[]) di seguito e quindi puoi accedere ai valori con i loro nomi nell'ordine che preferisci, ad esempio:

$ 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

Se può aiutare altri utenti con un problema simile, una proposta (dettagliata) che utilizza 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