sed regex
Eliminar duplicados del archivo de texto basado en el segundo archivo de texto (4)
¿Cómo puedo eliminar todas las líneas de un archivo de texto (
main.txt
) marcando un segundo archivo de texto (
main.txt
)?
¿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
Aquí hay muchas de las soluciones simples y efectivas que he encontrado: http://www.catonmat.net/blog/set-operations-in-unix-shell-simplified/
Necesita usar uno de los comandos bash de
Set Complement
.
Los archivos de 100 MB 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 -v e=''element'' ''$0 == e { s=1; exit } END { exit !s }''
Establecer 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:
# [email protected]
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 }''
Hay dos formas estándar de hacer esto:
Con
grep
:
grep -vxFf removethese main
Esto usa:
-
-v
para invertir el partido. -
-x
coincide con la línea completa, para evitar, por ejemplo,he
coincida con líneas comohello
ohighway to hell
. -
-F
para usar 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, deremovethese
.
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
e imprimimos las líneas que no están presentes en la matriz.
Me gusta el uso de awk de @ fedorqui para configuraciones donde uno tiene suficiente memoria para todas las líneas de "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 agitarse, 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 se ordenarán
- requiere suficiente disco para que la ordenación haga lo suyo (archivos temporales) y almacene versiones ordenadas del mismo tamaño de los archivos de entrada
- tener la opción -v de join hacer justo lo que queremos aquí: imprimir "no deseable" desde el archivo 1, soltar coincidencias, es un poco de casualidad
- no trata directamente las configuraciones regionales, la clasificación, las claves, etc. - se basa en los valores predeterminados de clasificación y unión (-t con un argumento vacío) para que coincida con el orden de clasificación, que funciona en mi máquina actual
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
newremovethese.txt
en el comando
sed
.
Pero 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
lleva 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
(¡solo
0.2403 seconds
!).
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
.