Si está buscando eliminar líneas de un archivo que "incluso contienen" cadenas de otro archivo (por ejemplo, coincidencias parciales) vea unix.stackexchange.com/questions/145079/…
rogerdpack
Respuestas:
154
grep -v -x -f f2 f1 debería hacer el truco.
Explicación:
-v para seleccionar líneas no coincidentes
-x para que coincida solo con líneas enteras
-f f2 para obtener patrones de f2
En su lugar se puede utilizar grep -Fo fgreppara que coincida con cuerdas fijas del f2lugar de los patrones (en caso de querer quitar las líneas de una manera "lo que se ve si lo que se obtiene" en lugar de tratar las líneas en f2que los patrones de expresiones regulares).
Esto tiene una complejidad O (n²) y comenzará a tardar horas en completarse una vez que los archivos contengan más de unas pocas líneas K.
Arnaud Le Blanc
11
Averiguar qué algoritmos sugeridos por SO tienen complejidad O (n ^ 2) solo tiene complejidad O (n), pero aún puede tomar horas para competir.
HDave
2
Acabo de probar esto en 2 archivos de ~ 2k líneas cada uno, y fue eliminado por el sistema operativo (concedido, esta es una máquina virtual no tan potente, pero aún así).
Trebor Rude
1
Me encanta la elegancia de esto; Prefiero la velocidad de la respuesta de Jona Christopher Sahnwal.
Alex Hall el
1
@ arnaud576875: ¿Estás seguro? Depende de la implementación de grep. Si se procesa f2correctamente antes de comenzar a buscar, la búsqueda solo tomará tiempo O (n).
Hola
56
Prueba comm en su lugar (suponiendo que f1 y f2 están "ya ordenados")
No estoy seguro de commque es la solución tiene la pregunta no indica que las líneas en f1se ordenan que es un requisito previo para el usocomm
gabuzo
1
Esto funcionó para mí, ya que mis archivos estaban ordenados y tenían más de 250,000 líneas en uno de ellos, solo 28,000 en el otro. ¡Gracias!
Invierno
1
Cuando esto funciona (los archivos de entrada están ordenados), ¡esto es extremadamente rápido!
Mike Jarvis
Al igual que en la solución de arnaud576875, para mí usar cygwin, esto eliminó las líneas duplicadas en el segundo archivo que es posible que desee conservar.
Alex Hall
8
Puede utilizar la sustitución de procesos para ordenar los archivos primero, por supuesto:comm -2 -3 <(sort f1) <(sort f2)
davemyron
14
Para excluir archivos que no son demasiado grandes, puede usar las matrices asociativas de AWK.
awk 'NR == FNR { list[tolower($0)]=1; next } { if (! list[tolower($0)]) print }' exclude-these.txt from-this.txt
La salida estará en el mismo orden que el archivo "from-this.txt". La tolower()función hace que no distinga entre mayúsculas y minúsculas, si lo necesita.
La complejidad algorítmica probablemente será O (n) (exclude-these.txt size) + O (n) (from-this.txt size)
¿Por qué dices archivos que no son demasiado grandes? El temor aquí es (supongo) que awk está ejecutando el sistema sin memoria del sistema para crear el hash, ¿o hay alguna otra limitación?
rogerdpack
para los seguidores, hay incluso otra opción más agresiva para "desinfectar" las líneas (ya que la comparación debe ser exacta para usar la matriz asociativa), ex unix.stackexchange.com/a/145132/8337
rogerdpack
@rogerdpack: un archivo de exclusión grande requerirá una gran matriz de hash (y un largo tiempo de procesamiento). Un gran "from-this.txt" solo requerirá un largo tiempo de procesamiento.
Pausado hasta nuevo aviso.
1
Esto falla (es decir, no produce ninguna salida) si exclude-these.txtestá vacío. La respuesta de @ jona-christopher-sahnwaldt a continuación funciona en este caso. También puede especificar varios archivos, por ejemploawk '{if (f==1) { r[$0] } else if (! ($0 in r)) { print $0 } } ' f=1 done.out failed.out f=2 all-files.out
Graham Russell
11
Similar a la respuesta de Dennis Williamson (en su mayoría cambios sintácticos, por ejemplo, establecer el número de archivo explícitamente en lugar del NR == FNRtruco):
El acceso r[$0]crea la entrada para esa línea, no es necesario establecer un valor.
Suponiendo que awk usa una tabla hash con búsqueda constante y (en promedio) tiempo de actualización constante, la complejidad del tiempo será O (n + m), donde n y m son las longitudes de los archivos. En mi caso, n fue de ~ 25 millones ym ~ 14000. La solución awk fue mucho más rápida que la clasificación, y también preferí mantener el orden original.
¿Cómo difiere esto de la respuesta de Dennis Williamson? ¿Es la única diferencia que no hace una asignación en el hash, tan ligeramente más rápido que esto? ¿La complejidad algorítmica es igual a la suya?
rogerdpack
La diferencia es principalmente sintáctica. Encuentro la variable fmás clara que NR == FNR, pero eso es cuestión de gustos. La asignación al hash debe ser tan rápida que no haya una diferencia de velocidad medible entre las dos versiones. Creo que estaba equivocado acerca de la complejidad: si la búsqueda es constante, la actualización también debería ser constante (en promedio). No sé por qué pensé que la actualización sería logarítmica. Editaré mi respuesta.
jcsahnwaldt Restablece a Monica el
Intenté muchas de estas respuestas, y esta fue AMAZEBALLS rápidamente. Tenía archivos con cientos de miles de líneas. ¡Trabajado como un encanto!
Sr. T
1
Esta es mi solución preferida. Funciona con múltiples archivos y también con archivos de exclusión vacíos, por ejemplo awk '{if (f==1) { r[$0] } else if (! ($0 in r)) { print $0 } } ' f=1 empty.file done.out failed.out f=2 all-files.out. Mientras que la otra awksolución falla con el archivo de exclusión vacío y solo puede tomar uno.
Graham Russell
5
si tienes Ruby (1.9+)
#!/usr/bin/env ruby
b=File.read("file2").split
open("file1").each do|x|
x.chomp!
puts x if!b.include?(x)
end
Que tiene complejidad O (N ^ 2). Si quieres preocuparte por el rendimiento, aquí hay otra versión
que usa un hash para efectuar la resta, también lo es la complejidad O (n) (tamaño de a) + O (n) (tamaño de b)
Aquí hay un pequeño punto de referencia, cortesía del usuario576875, pero con 100K líneas, de lo anterior:
$ for i in $(seq 1100000);do echo "$i";done|sort --random-sort > file1
$ for i in $(seq 12100000);do echo "$i";done|sort --random-sort > file2
$ time ruby test.rb > ruby.test
real 0m0.639s
user 0m0.554s
sys 0m0.021s
$time sort file1 file2|uniq -u > sort.test
real 0m2.311s
user 0m1.959s
sys 0m0.040s
$ diff <(sort -n ruby.test)<(sort -n sort.test)
$
diff se utilizó para mostrar que no hay diferencias entre los 2 archivos generados.
Esto tiene una complejidad O (n²) y comenzará a tardar horas en completarse una vez que los archivos contengan más de unas pocas líneas K.
Arnaud Le Blanc
Realmente no me importa en este momento, porque no mencionó ningún archivo grande.
kurumi
3
No hay necesidad de estar tan a la defensiva, no es como si @ user576875 hubiera rechazado su respuesta o algo así. :-)
John Parker
muy buena segunda versión, Ruby gana :)
Arnaud Le Blanc
4
Algunas comparaciones de tiempo entre varias otras respuestas:
$ for n in{1..10000};do echo $RANDOM;done> f1
$ for n in{1..10000};do echo $RANDOM;done> f2
$ time comm -23<(sort f1)<(sort f2)>/dev/null
real 0m0.019s
user 0m0.023s
sys 0m0.012s
$ time ruby -e 'puts File.readlines("f1") - File.readlines("f2")'>/dev/null
real 0m0.026s
user 0m0.018s
sys 0m0.007s
$ time grep -xvf f2 f1 >/dev/null
real 0m43.197s
user 0m43.155s
sys 0m0.040s
sort f1 f2 | uniq -u ni siquiera es una diferencia simétrica, porque elimina las líneas que aparecen varias veces en cualquier archivo.
comm también se puede usar con stdin y aquí cadenas:
echo $'a\nb'| comm -23<(sort)<(sort <<< $'c\nb')# a
Parece ser un trabajo adecuado para el shell SQLite:
create table file1(line text);
create index if1 on file1(line ASC);
create table file2(line text);
create index if2 on file2(line ASC);-- comment:if you have |in your files then specify “.separator ××any_improbable_string××”.import 'file1.txt' file1
.import 'file2.txt' file2
.output result.txt
select * from file2 where line not in(select line from file1);.q
Obviamente no funcionará para archivos grandes, pero me sirvió. Algunas notas
No estoy afiliado al sitio web de ninguna manera (si todavía no me cree, puede buscar una herramienta diferente en línea; utilicé el término de búsqueda "establecer lista de diferencias en línea")
El sitio web vinculado parece hacer llamadas de red en cada comparación de lista, así que no le des datos confidenciales
Respuestas:
grep -v -x -f f2 f1
debería hacer el truco.Explicación:
-v
para seleccionar líneas no coincidentes-x
para que coincida solo con líneas enteras-f f2
para obtener patrones def2
En su lugar se puede utilizar
grep -F
ofgrep
para que coincida con cuerdas fijas delf2
lugar de los patrones (en caso de querer quitar las líneas de una manera "lo que se ve si lo que se obtiene" en lugar de tratar las líneas enf2
que los patrones de expresiones regulares).fuente
grep
. Si se procesaf2
correctamente antes de comenzar a buscar, la búsqueda solo tomará tiempo O (n).Prueba comm en su lugar (suponiendo que f1 y f2 están "ya ordenados")
fuente
comm
que es la solución tiene la pregunta no indica que las líneas enf1
se ordenan que es un requisito previo para el usocomm
comm -2 -3 <(sort f1) <(sort f2)
Para excluir archivos que no son demasiado grandes, puede usar las matrices asociativas de AWK.
La salida estará en el mismo orden que el archivo "from-this.txt". La
tolower()
función hace que no distinga entre mayúsculas y minúsculas, si lo necesita.La complejidad algorítmica probablemente será O (n) (exclude-these.txt size) + O (n) (from-this.txt size)
fuente
exclude-these.txt
está vacío. La respuesta de @ jona-christopher-sahnwaldt a continuación funciona en este caso. También puede especificar varios archivos, por ejemploawk '{if (f==1) { r[$0] } else if (! ($0 in r)) { print $0 } } ' f=1 done.out failed.out f=2 all-files.out
Similar a la respuesta de Dennis Williamson (en su mayoría cambios sintácticos, por ejemplo, establecer el número de archivo explícitamente en lugar del
NR == FNR
truco):awk '{if (f==1) { r[$0] } else if (! ($0 in r)) { print $0 } } ' f=1 exclude-these.txt f=2 from-this.txt
El acceso
r[$0]
crea la entrada para esa línea, no es necesario establecer un valor.Suponiendo que awk usa una tabla hash con búsqueda constante y (en promedio) tiempo de actualización constante, la complejidad del tiempo será O (n + m), donde n y m son las longitudes de los archivos. En mi caso, n fue de ~ 25 millones ym ~ 14000. La solución awk fue mucho más rápida que la clasificación, y también preferí mantener el orden original.
fuente
f
más clara queNR == FNR
, pero eso es cuestión de gustos. La asignación al hash debe ser tan rápida que no haya una diferencia de velocidad medible entre las dos versiones. Creo que estaba equivocado acerca de la complejidad: si la búsqueda es constante, la actualización también debería ser constante (en promedio). No sé por qué pensé que la actualización sería logarítmica. Editaré mi respuesta.awk '{if (f==1) { r[$0] } else if (! ($0 in r)) { print $0 } } ' f=1 empty.file done.out failed.out f=2 all-files.out
. Mientras que la otraawk
solución falla con el archivo de exclusión vacío y solo puede tomar uno.si tienes Ruby (1.9+)
Que tiene complejidad O (N ^ 2). Si quieres preocuparte por el rendimiento, aquí hay otra versión
que usa un hash para efectuar la resta, también lo es la complejidad O (n) (tamaño de a) + O (n) (tamaño de b)
Aquí hay un pequeño punto de referencia, cortesía del usuario576875, pero con 100K líneas, de lo anterior:
diff
se utilizó para mostrar que no hay diferencias entre los 2 archivos generados.fuente
Algunas comparaciones de tiempo entre varias otras respuestas:
sort f1 f2 | uniq -u
ni siquiera es una diferencia simétrica, porque elimina las líneas que aparecen varias veces en cualquier archivo.comm también se puede usar con stdin y aquí cadenas:
fuente
Parece ser un trabajo adecuado para el shell SQLite:
fuente
¿Intentaste esto con sed?
fuente
No es una respuesta de 'programación', pero aquí hay una solución rápida y sucia: solo vaya a http://www.listdiff.com/compare-2-lists-difference-tool .
Obviamente no funcionará para archivos grandes, pero me sirvió. Algunas notas
fuente