Tengo un archivo f1:
line1
line2
line3
line4
..
..
Quiero eliminar todas las líneas que están en otro archivo f2:
line2
line8
..
..
Intenté algo con caty sed, que ni siquiera estaba cerca de lo que pretendía. ¿Cómo puedo hacer esto?
Tengo un archivo f1:
line1
line2
line3
line4
..
..
Quiero eliminar todas las líneas que están en otro archivo f2:
line2
line8
..
..
Intenté algo con caty sed, que ni siquiera estaba cerca de lo que pretendía. ¿Cómo puedo hacer esto?
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 de f2En 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).
grep. Si se procesa f2correctamente 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")
comm -2 -3 f1 f2
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
comm -2 -3 <(sort f1) <(sort f2)
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)
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
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):
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.
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.
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.
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
b=File.read("file2").split
a=File.read("file1").split
(a-b).each {|x| puts x}
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 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.
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
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