2011-01-24 4 views
94

que tienen un archivo f1:líneas Borrado de un archivo que se encuentran en otro archivo

line1 
line2 
line3 
line4 
.. 
.. 

quiero borrar todas las líneas que están en otro archivo f2:

line2 
line8 
.. 
.. 

Probé algo con cat y sed, que ni siquiera estaba cerca de lo que pretendía. ¿Cómo puedo hacer esto?

+4

posible duplicado de [Quitar líneas de archivos que aparecen en otro archivo] (http://stackoverflow.com/questions/4366533/remove-lines-from-file-which-appear-in-another-file) –

+0

Si está buscando eliminar líneas de un archivo que "incluso contiene" cadenas de otro archivo (por ejemplo, coincidencias parciales), consulte http://unix.stackexchange.com/questions/145079/remove-all-lines-in- file-a-which-contain-the-strings-in-file-b – rogerdpack

Respuesta

113

grep -v -x -f f2 f1 debería hacer el truco.

Explicación:

  • -v para seleccionar líneas no coincidentes
  • -x para que coincida con las líneas enteras solamente
  • -f f2 para obtener patrones de f2

Una vez puede utilizar -F f2 para que coincida cadenas fijas de f2 en lugar de patrones (en caso de que desee eliminar las líneas de una manera "lo que ve si obtiene lo que obtiene" en lugar de tratar las líneas en f2 como patrones regex).

+20

Esto tiene complejidad O (n²) y comenzará a tomar horas para completarse una vez que los archivos contienen más de unas pocas líneas K. – arnaud576875

+5

Averiguar qué SO algoritmos sugeridos tienen O (n^2) complejidad solo tiene O (n) complejidad, pero aún puede tomar horas para competir. – HDave

+2

Acabo de probar esto en 2 archivos de ~ 2k líneas cada uno, y el sistema operativo lo mató (se le concede, esta es una máquina virtual no tan poderosa, pero aún así). –

40

Try comm lugar (suponiendo F1 y F2 están "ya ordenados")

comm -2 -3 f1 f2 
+5

No estoy seguro 'comm' es la solución tiene la pregunta no indica que las líneas en' f1' están ordenadas, que es un requisito previo para usar 'comm' – gabuzo

+1

Esto funcionó para mí, ya que mis archivos fueron ordenados y tenía 250,000 + líneas en una de ellas, solo 28,000 en la otra. ¡Gracias! – Winter

+1

Cuando esto funciona (los archivos de entrada están ordenados), ¡esto es extremadamente rápido! –

4

si tiene Rubí (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 O (n^2) la complejidad. Si quieres que se preocupan por el rendimiento, aquí es otra versión

b=File.read("file2").split 
a=File.read("file1").split 
(a-b).each {|x| puts x} 

que utiliza un hash para efectuar la sustracción, por lo que 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 de user576875, pero con líneas de 100K, de lo anterior:

$ for i in $(seq 1 100000); do echo "$i"; done|sort --random-sort > file1 
$ for i in $(seq 1 2 100000); 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.

+1

Esto tiene complejidad O (n²) y comenzará a tomar horas para completarse una vez los archivos contienen más de unas pocas líneas K. – arnaud576875

+0

realmente no me importa en este momento, porque él no mencionó ningún archivo grande. – kurumi

+3

No hay necesidad de estar tan a la defensiva, no es como si @usuario576875 rechazara su respuesta ni nada por el estilo. :-) –

2

parece ser un trabajo adecuado para la cáscara de 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 
11

Para excluir archivos que no son demasiado grandes, puede utilizar 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 función tolower() lo hace insensible a las mayúsculas y minúsculas, si lo necesita.

La complejidad algorítmica probablemente será O (n) (exclude-these.txt size) + O (n) (from-this.tamaño txt)

+0

¿Por qué dices archivos que no son demasiado grandes? El temor aquí es (supongo) que ejecutar el sistema fuera de la memoria del sistema para crear el hash, ¿o hay alguna otra limitación? – rogerdpack

+0

para seguidores, hay incluso otra opción más agresiva para "desinfectar" las líneas (ya que la comparación tiene que ser exacta para usar la matriz asociativa), ex http://unix.stackexchange.com/a/145132/8337 – rogerdpack

+0

@ 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. –

1

Algunas comparaciones temporales 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 no es ni siquiera una diferencia simétrica, ya que elimina las líneas que aparecen varias veces en cualquiera de los archivos.

comunicación también se puede usar con stdin y aquí cuerdas:

echo $'a\nb' | comm -23 <(sort) <(sort <<< $'c\nb') # a 
8

similares a la respuesta de Dennis Williamson (en su mayoría los cambios sintácticos, por ejemplo, el ajuste del número de archivos de forma explícita en lugar de la 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

Al acceder a r[$0] se 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 temporal de esto será O (n + m), donde n y m son las longitudes de los archivos. En mi caso, n fue ~ 25 millones y m ~ 14000. La solución awk fue mucho más rápida que la ordenada, y también preferí mantener el orden original.

+0

¿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 la misma que la de él? – rogerdpack

+0

La diferencia es principalmente sintáctica. Encuentro la variable '' 'f''' más clara que' '' NR == FNR''', pero eso es una cuestión de gusto. La asignación en el hash debe ser tan rápida que no haya una diferencia de velocidad mensurable entre las dos versiones. Creo que estaba equivocado sobre 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. –

+0

Intenté un montón de estas respuestas, y esta fue AMAZEBALLS rápido. Tenía archivos con cientos de miles de líneas. ¡Trabajado como un encanto! –

1

¿Has probado este con sed?

sed 's#^#sed -i '"'"'s%#g' f2 > f2.sh 

sed -i 's#$#%%g'"'"' f1#g' f2.sh 

sed -i '1i#!/bin/bash' f2.sh 

sh f2.sh 
Cuestiones relacionadas