Eliminar duplicados del archivo de texto basado en el segundo archivo de texto

¿Cómo puedo eliminar todas las líneas de un archivo de texto ( main.txt ) al verificar un segundo archivo de texto ( main.txt archivo)? ¿Cuál es un enfoque eficiente si los archivos son mayores de 10-100mb. [Usando mac]

Ejemplo:

 main.txt 3 1 2 5 

Eliminar estas líneas

 removethese.txt 3 2 9 

Salida:

 output.txt 1 5 

Líneas de ejemplo (estas son las líneas reales con las que estoy trabajando – el orden no importa):

 ChIJW3p7Xz8YyIkRBD_TjKGJRS0 ChIJ08x-0kMayIkR5CcrF-xT6ZA ChIJIxbjOykFyIkRzugZZ6tio1U ChIJiaF4aOoEyIkR2c9WYapWDxM ChIJ39HoPKDix4kRcfdIrxIVrqs ChIJk5nEV8cHyIkRIhmxieR5ak8 ChIJs9INbrcfyIkRf0zLkA1NJEg ChIJRycysg0cyIkRArqaCTwZ-E8 ChIJC8haxlUDyIkRfSfJOqwe698 ChIJxRVp80zpcEARAVmzvlCwA24 ChIJw8_LAaEEyIkR68nb8cpalSU ChIJs35yqObit4kR05F4CXSHd_8 ChIJoRmgSdwGyIkRvLbhOE7xAHQ ChIJaTtWBAWyVogRcpPDYK42-Nc ChIJTUjGAqunVogR90Kc8hriW8c ChIJN7P2NF8eVIgRwXdZeCjL5EQ ChIJizGc0lsbVIgRDlIs85M5dBs ChIJc8h6ZqccVIgR7u5aefJxjjc ChIJ6YMOvOeYVogRjjCMCL6oQco ChIJ54HcCsaeVogRIy9___RGZ6o ChIJif92qn2YVogR87n0-9R5tLA ChIJ0T5e1YaYVogRifrl7S_oeM8 ChIJwWGce4eYVogRcrfC5pvzNd4 

Hay dos formas estándar de hacer esto:

Con grep :

 grep -vxFf removethese main 

Esto usa:

  • -v para invertir el partido.
  • -x coincide con toda la línea, para evitar, por ejemplo, he unir líneas como hello o highway to hell .
  • -F para utilizar cadenas fijas, de modo que el parámetro se tome como está, no se interprete como una expresión regular.
  • -f para obtener los patrones de otro archivo. En este caso, de removethese .

Con awk :

 $ awk 'FNR==NR {a[$0];next} !($0 in a)' removethese main 1 5 

De esta manera almacenamos cada línea en removethese en una matriz a[] . Luego, leemos el archivo main y simplemente imprimimos aquellas líneas que no están presentes en la matriz.

Con grep :

 grep -vxFf removethese.txt main.txt >output.txt 

Con fgrep :

 fgrep -vxf removethese.txt main.txt >output.txt 

fgrep está en desuso. fgrep --help dice:

La invocación como ‘fgrep’ está en desuso; use ‘grep -F’ en su lugar.

Con awk (de @fedorqui):

 awk 'FNR==NR {a[$0];next} !($0 in a)' removethese.txt main.txt >output.txt 

Con sed :

 sed "s=^=/^=;s=$=$/d=" removethese.txt | sed -f- main.txt >output.txt 

Esto fallará si removethese.txt contiene caracteres especiales. Para eso puedes hacer:

 sed 's/[^^]/[&]/g; s/\^/\\^/g' removethese.txt >newremovethese.txt 

y use este nuevoremovethese.txt en el comando sed . Pero esto no vale la pena el esfuerzo, es demasiado lento en comparación con los otros métodos.


Prueba realizada en los métodos anteriores:

El método sed toma demasiado tiempo y no vale la pena probarlo.

Archivos utilizados:

 removethese.txt : Size: 15191908 (15MB) Blocks: 29672 Lines: 100233 main.txt : Size: 27640864 (27.6MB) Blocks: 53992 Lines: 180034 

Comandos:
grep -vxFf | fgrep -vxf | awk

Tiempo tomado:
0m7.966s | 0m7.823s | 0m0.237s
0m7.877s | 0m7.889s | 0m0.241s
0m7.971s | 0m7.844s | 0m0.234s
0m7.864s | 0m7.840s | 0m0.251s
0m7.798s | 0m7.672s | 0m0.238s
0m7.793s | 0m8.013s | 0m0.241s

AVG
0m7.8782s | 0m7.8468s | 0m0.2403s

El resultado de esta prueba implica que fgrep es un poco más rápido que grep .

El método awk (de @fedorqui) pasa la prueba con gran 0.2403 seconds ( 0.2403 seconds solamente !!!).

Entorno de prueba:

 HP ProBook 440 G1 Laptop 8GB RAM 2.5GHz processor with turbo boost upto 3.1GHz RAM being used: 2.1GB Swap being used: 588MB RAM being used when the grep/fgrep command is run: 3.5GB RAM being used when the awk command is run: 2.2GB or less Swap being used when the commands are run: 588MB (No change) 

Resultado de la prueba:

Usa el método awk .

Me gusta el uso de @fedorqui de awk para las configuraciones en las que uno tiene suficiente memoria para todas las líneas “eliminar estas”: una expresión concisa de un enfoque en memoria.

Pero para un escenario donde el tamaño de las líneas para eliminar es grande en relación con la memoria actual, y leer esos datos en una estructura de datos en memoria es una invitación a fallar o demoler, considere un enfoque antiguo: ordenar / unir

 sort main.txt > main_sorted.txt sort removethese.txt > removethese_sorted.txt join -t '' -v 1 main_sorted.txt removethese_sorted.txt > output.txt 

Notas:

  • esto no conserva el orden de main.txt: las líneas en output.txt serán ordenadas
  • requiere que haya suficiente disco para permitir que el género haga su trabajo (archivos temporales) y almacenar las versiones clasificadas del mismo tamaño de los archivos de entrada
  • tener la opción -v de join hace justo lo que queremos aquí – imprimir “indeseable” desde el archivo 1, soltar coincidencias – es un poco de casualidad
  • no se refiere directamente a las configuraciones regionales, la clasificación, las claves, etc. – se basa en los valores predeterminados de ordenar y unirse (-t con un argumento vacío) para hacer coincidir el orden de clasificación, que funciona en mi máquina actual

Aquí hay muchas soluciones simples y efectivas que he encontrado: http://www.catonmat.net/blog/set-operations-in-unix-shell-simplified/

Necesita usar uno de los comandos Set Complement bash. Los archivos de 100MB se pueden resolver en segundos o minutos.

Establecer membresía

 $ grep -xc 'element' set # outputs 1 if element is in set # outputs >1 if set is a multi-set # outputs 0 if element is not in set $ grep -xq 'element' set # returns 0 (true) if element is in set # returns 1 (false) if element is not in set $ awk '$0 == "element" { s=1; exit } END { exit !s }' set # returns 0 if element is in set, 1 otherwise. $ awk -ve='element' '$0 == e { s=1; exit } END { exit !s }' 

Establecer la igualdad

 $ diff -q <(sort set1) <(sort set2) # returns 0 if set1 is equal to set2 # returns 1 if set1 != set2 $ diff -q <(sort set1 | uniq) <(sort set2 | uniq) # collapses multi-sets into sets and does the same as previous $ awk '{ if (!($0 in a)) c++; a[$0] } END{ exit !(c==NR/2) }' set1 set2 # returns 0 if set1 == set2 # returns 1 if set1 != set2 $ awk '{ a[$0] } END{ exit !(length(a)==NR/2) }' set1 set2 # same as previous, requires >= gnu awk 3.1.5 

Establecer cardinalidad

 $ wc -l set | cut -d' ' -f1 # outputs number of elements in set $ wc -l < set $ awk 'END { print NR }' set 

Prueba de subconjunto

 $ comm -23 <(sort subset | uniq) <(sort set | uniq) | head -1 # outputs something if subset is not a subset of set # does not putput anything if subset is a subset of set $ awk 'NR==FNR { a[$0]; next } { if !($0 in a) exit 1 }' set subset # returns 0 if subset is a subset of set # returns 1 if subset is not a subset of set 

Establecer Unión

 $ cat set1 set2 # outputs union of set1 and set2 # assumes they are disjoint $ awk 1 set1 set2 # ditto $ cat set1 set2 ... setn # union over n sets $ cat set1 set2 | sort -u # same, but assumes they are not disjoint $ sort set1 set2 | uniq # sort -u set1 set2 $ awk '!a[$0]++' # ditto 

Establecer intersección

 $ comm -12 <(sort set1) <(sort set2) # outputs insersect of set1 and set2 $ grep -xF -f set1 set2 $ sort set1 set2 | uniq -d $ join <(sort -n A) <(sort -n B) $ awk 'NR==FNR { a[$0]; next } $0 in a' set1 set2 

Establecer complemento

 $ comm -23 <(sort set1) <(sort set2) # outputs elements in set1 that are not in set2 $ grep -vxF -f set2 set1 # ditto $ sort set2 set2 set1 | uniq -u # ditto $ awk 'NR==FNR { a[$0]; next } !($0 in a)' set2 set1 

Establecer diferencia simétrica

 $ comm -3 <(sort set1) <(sort set2) | sed 's/\t//g' # outputs elements that are in set1 or in set2 but not both $ comm -3 <(sort set1) <(sort set2) | tr -d '\t' $ sort set1 set2 | uniq -u $ cat <(grep -vxF -f set1 set2) <(grep -vxF -f set2 set1) $ grep -vxF -f set1 set2; grep -vxF -f set2 set1 $ awk 'NR==FNR { a[$0]; next } $0 in a { delete a[$0]; next } 1; END { for (b in a) print b }' set1 set2 

Set de poder

 $ p() { [ $# -eq 0 ] && echo || (shift; p "$@") | while read r ; do echo -e "$1 $r\n$r"; done } $ p `cat set` # no nice awk solution, you are welcome to email me one: # peter@catonmat.net 

Establecer producto cartesiano

 $ while read a; do while read b; do echo "$a, $b"; done < set1; done < set2 $ awk 'NR==FNR { a[$0]; next } { for (i in a) print i, $0 }' set1 set2 

Prueba de conjunto disjunto

 $ comm -12 <(sort set1) <(sort set2) # does not output anything if disjoint $ awk '++seen[$0] == 2 { exit 1 }' set1 set2 # returns 0 if disjoint # returns 1 if not 

Prueba de conjunto vacío

 $ wc -l < set # outputs 0 if the set is empty # outputs >0 if the set is not empty $ awk '{ exit 1 }' set # returns 0 if set is empty, 1 otherwise 

Mínimo

 $ head -1 <(sort set) # outputs the minimum element in the set $ awk 'NR == 1 { min = $0 } $0 < min { min = $0 } END { print min }' 

Máximo

 $ tail -1 <(sort set) # outputs the maximum element in the set $ awk '$0 > max { max = $0 } END { print max }'