Eliminar líneas de un archivo dependiendo de las líneas encontradas en otro archivo

11

El archivo file1.txt contiene líneas como:

/api/purchase/<hash>/index.html

Por ejemplo:

/api/purchase/12ab09f46/index.html

El archivo file2.csv contiene líneas como:

<hash>,timestamp,ip_address

Por ejemplo:

12ab09f46,20150812235200,22.231.113.64 
a77b3ff22,20150812235959,194.66.82.11

Quiero filtrar file2.csv eliminando todas las líneas donde el valor de hash está presente también en file1.txt. Que quiere decir:

cat file1.txt | extract <hash> | sed '/<hash>/d' file2.csv

o algo como esto.

Debería ser sencillo, pero parece que no puedo hacerlo funcionar.

¿Alguien puede proporcionar una tubería de trabajo para esta tarea?

Marco Faustinelli
fuente

Respuestas:

13

cut -d / -f 4 file1.txt | paste -sd '|' | xargs -I{} grep -v -E {} file2.csv

Explicación:

cut -d / -f 4 file1.txt seleccionará los hashes del primer archivo

paste -sd '|' unirá todos los hashes en una expresión regular ej. H1|H2|H3

xargs -I{} grep -v -E {} file2.csvinvocará grep con el patrón anterior como argumento, xargs reemplazará {}con el contenido delSTDIN

Si no tiene paste, puede reemplazarlo contr "\\n" "|" | sed 's/|$//'

Gabriele Lana
fuente
3
+1 pero no es necesario cat, solo cut -d / -f 4 file1.txt. O si prefiere el aspecto secuencial,<file1.txt cut -d / -f 4
Sparhawk
@Sparhawk gracias! No sabía ;-) solución actualizada :-)
Gabriele Lana
11

Posible awksolución:

awk 'NR == FNR { x[$4] = 1; next; } { if (!($1 in x)) print $0; }' FS="/" file1.txt FS="," file2.txt

Primero leemos file1.txtusando FS(separador de campo) "/" y creamos una matriz x con valores de claves del campo, $4que es el hash que desea. A continuación, leemos la file2.txtconfiguración del segundo archivo FSpara ser ,y verificamos si el valor del campo $1no existe como clave en la matriz xy si no lo imprimimos.
Lo mismo más idiomático como se propone en los comentarios podría ser:

awk 'NR == FNR { x[$4] = 1; next; } !($1 in x)' FS="/" file1.txt FS="," file2.txt
taliezin
fuente
Aprecio tu esfuerzo, pero me temo que esto vuela por encima de mi cabeza. Sigo esperando que sea posible una solución basada en una mezcla sed / grep / cat.
Marco Faustinelli
1
Agregaré una explicación, es simple. Y puede ser que alguien le proponga una solución con las herramientas que desee.
taliezin
¿Por qué no solo en !($1 in x)lugar de{ if (!($1 in x)) print $0; }
iruvar
@ 1_CR es mi mal hábito, sé que podría ser más idiomático, pero siempre creo que será más sencillo explicarlo a OP.
taliezin
@Muzietto aún, creo que no hay nada malo en comenzar a aprender otras herramientas como esta awksolución basada ... a la larga, aprenderá a gravitar hacia soluciones que se pueden lograr utilizando tuberías menores para simplificar ... :)
hjk
5

Para GNU sed

sed -z 's%.*/\([^/]*\)/index.html\n%\1\\|%g;s%^%/%;s%\\|$%/d%' file1.csv |
sed -f - file2.csv

donde primero sed lista de productos de hashes en orden sed-formato como /12ab09f46\|a77b3ff22\|..../dy transferirla al siguiente SED -script que se lee por encima de comando desde por lo tanto, de entrada -f -opción.
Lo mismo con grep

grep -oP '[^/]*(?=/index.html$)' file1.csv | grep -Fvf - file2.csv

o sin expresiones perl:

grep -o '[^/]*/index.html$' file1.csv | 
grep -o '^[^/]*' | 
grep -Fvf - file2.csv

o incluso mejor con corte :

cut -d/ -f4 file1.csv | grep -Fvf - file2.csv
Costas
fuente
Esto me parece lo que estaba buscando. ¿Puedes por favor ilustrarlo un poco? No puedo ver cómo el segundo comando eliminará las líneas de file2.csv.
Marco Faustinelli
@Muzietto Ver actualizado
Costas
2
#!/bin/bash
cut -d, -f1 file2 | while read key ; do 
   #check for appearance in file1 with successful grep:
   #exit status is 0 if pattern is found, only search for at least 1
   #appearance -> to speed it up
   if [[ $(grep -m 1 "/$key/" file1) ]] ; then
      sed "/^$key,/d" -i file2
      #note that we are gradually overwriting file2 (-i option),
      #so make a backup!
   fi
done

Tenga en cuenta que las picaduras de búsqueda son /$key/y ^$key,para reducir los resultados, ya sea entre dos barras (archivo 1) o como la primera entrada de una línea y seguidas de una coma (archivo 2). Esto debería hacerlo seguro si las teclas se ven como

a,values
a1,values

en el archivo 2, o como

/api/../a1/../
/api/../a/../

en el archivo 1

Fiximan
fuente
2

Acabo de probar el siguiente revestimiento, y parece hacer el trabajo:

 for i in `cat file1.txt  | awk -F"/" '{print $4}'`; do echo "\n $i" ; sed -ri "/^$i,/d" file2.csv ; done

Reemplace first -ri con -re para probarlo. -re realiza una ejecución en seco, y si todo está bien, puede ejecutarlo con -ri

primero
fuente
mmmh, he redirigido la salida de su código a un archivo temporal y contiene aproximadamente 30k líneas, mientras que file2.csv tiene inicialmente 240 y se supone que debe filtrarse.
Marco Faustinelli
Bueno, creo que es porque imprimo cada hash en el primer archivo, cuando hago la sustitución (el eco "\ n" $ i parte). De todos modos, si lo ejecutas con -ri no tienes que redirigir, porque realiza la sustitución en su lugar
primero
Además, si ejecuta con -re y redirige, repetirá file2 para todos los hashes que tenga en el primer archivo. Básicamente para cada hash en el primer archivo, lo reemplaza en el segundo archivo e imprime el resultado, por eso tiene tantas líneas.
primero
1

Además de la respuesta de Gabriele Lana, tenga en cuenta que el comando de pegado BSD necesita que se especifique el guión para leer el contenido de la entrada estándar.

manual de comando pegar

Si se especifica '-' para uno o más de los archivos de entrada, se usa la entrada estándar; la entrada estándar se lee una línea a la vez, circularmente, para cada instancia de '-'.

Entonces, la necesidad final debe ser el cambio como a continuación

cut -d / -f 4 file1.txt | paste -sd '|' - | xargs -I{} grep -v -E {} file2.csv
efesaid
fuente