Extraiga varios patrones de la línea independientemente del orden

Aug 20 2020

Soy nuevo en las secuencias de comandos de Unix, así que tengan paciencia conmigo.

Recibo un archivo que tiene información sobre los procesos en cada línea. Necesito extraer cierta información sobre estos procesos de cada línea.

Ejemplo del archivo -

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>

La salida deseada es -

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

Los números 1, 2 y 3 en cada línea solo indican el número de línea del archivo de salida.

Ya intenté usar el sed s/comando, pero es un orden específico, mientras que los parámetros en el archivo de entrada no siguen un orden; como resultado, se omiten algunas líneas en el archivo de entrada.

Aquí está mi comando:

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

¿Alguien podría orientarme sobre cómo extraer esos parámetros independientemente del orden?

¡Gracias!

Edición 1: Me las arreglé para usar la función de afirmación de ancho cero de grep de esta manera:

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

pero esto parece dar la salida para cada línea en nuevas líneas, es decir

1234
true
dummyAppId1

Tratando de averiguar cómo ponerlo en una línea usando grep (es decir, no fusionando líneas X en 1)

Edición 2: mezcla del orden de los parámetros en la entrada

Edición 3: lo siento, debería haber mencionado esto antes, perlparece estar restringido en las máquinas en las que trabajo. Si bien las respuestas proporcionadas por Stephane y Sundeep funcionan perfectamente cuando las pruebo localmente, no funcionarían en las máquinas en las que finalmente necesito que se ejecuten. Parece que awk, grep y sed son las opciones soportadas principalmente :(

Respuestas

8 Sundeep Aug 20 2020 at 13:06

Con awk(probado con GNU awk, no estoy seguro si funciona con otras implementaciones)

$ 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=[^ ]+)) primer grupo de captura para port
  • (?=.*(authenticate=[^ ]+))segundo grupo de captura para authenticatey así sucesivamente
  • print $. para el número de línea
  • Para evitar coincidencias parciales, utilice \bport, \bappIDetc. , si el límite de palabras es suficiente. De lo contrario, utilice (?<!\S)(port=[^ ]+)para restringir según los espacios en blanco.

Si necesita imprimir solo líneas que contengan appIDo cualquier otra condición similar, cambie -lpeay -lnecambie print $.aprint "$.\n$_" if /appID/

7 StéphaneChazelas Aug 20 2020 at 13:15

Con perl, podría utilizar un enfoque como:

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

Donde construye una tabla hash cuyas claves son los nombres de los atributos y los valores son los name=valuesy imprime los que desea después.

Reemplace $&con $2si solo desea los valores en la salida.

Lo mismo 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, puedes hacer:

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

(ese requiere que los tres atributos estén presentes).

Con sed:

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

Los dos últimos asumen que los atributos no son la primera palabra de la línea (lo que parece una suposición razonable dada su muestra).

1 LL3 Aug 20 2020 at 22:21

Cumpliendo con su EDIT 3, creo que aún podría hacerlo sedsi creara una s///expresión para cada parámetro como esta:

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'

Tenga en cuenta el orden inverso de las sexpresiones con respecto al orden de salida deseado. La numeración también está incrustada en el script, imprimiendo los números de línea de salida como mencionó, e imprime una línea solo si alguno de los parámetros deseados está realmente presente en una línea. Tenga en cuenta también que estoy aprovechando la sed sintaxis GNU ya que ha estado usando \dátomos que AFAIK no conoce BSD sed. Un equivalente compatible con POSIX podría ser posible, pero probablemente sería más expandido.

Sin embargo, eso ya es terriblemente largo y se volvería cada vez más complejo con el aumento de los parámetros de salida, por lo que un awkscript como el siguiente podría ser más versátil:

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

Debería especificar los parámetros exactos que desea generar, y su orden de aparición, como argumentos para el awk propio script después de --. Este script también imprime una línea solo cuando al menos uno de los parámetros deseados está realmente presente en una línea.

1 EdMorton Aug 21 2020 at 20:27

Cada vez que hay pares de nombre = valor en la entrada, creo que es mejor crear primero una matriz que contenga ese mapeo ( f[]) a continuación y luego puede acceder a los valores por sus nombres en el orden que desee, por ejemplo:

$ 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

Si puede ayudar a otros usuarios con un problema similar, una propuesta (detallada) usando 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