Estrai più modelli dalla linea indipendentemente dall'ordine
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 - perl
sembra 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
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 perport
(?=.*(authenticate=[^ ]+))
secondo gruppo di acquisizione perauthenticate
e così viaprint $.
per il numero di riga- Per evitare corrispondenze parziali, utilizzare
\bport
,\bappID
ecc. Se il confine di parola è sufficiente. In caso contrario, utilizzare(?<!\S)(port=[^ ]+)
per limitare in base agli spazi.
Se è necessario stampare solo righe contenenti appID
o qualsiasi altra condizione simile, passare -lpe
ae -lne
passare print $.
aprint "$.\n$_" if /appID/
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=value
i se stampa quelli che vuoi in seguito.
Sostituisci $&
con $2
se 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 pcregrep
puoi 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).
Rispettando il tuo EDIT 3, penso che potresti ancora farlo sed
se 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 s
espressioni 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 \d
atomi 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 awk
script 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.
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
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