Eliminazione di righe da un file che si trovano in un altro file


126

Ho un file f1:

line1
line2
line3
line4
..
..

Voglio eliminare tutte le righe che si trovano in un altro file f2:

line2
line8
..
..

Ho provato qualcosa con cate sed, che non era nemmeno vicino a quello che volevo. Come posso fare questo?



Se stai cercando di rimuovere righe da un file che "contiene anche" stringhe da un altro file (per esempio corrispondenze parziali) vedi unix.stackexchange.com/questions/145079/…
rogerdpack

Risposte:


154

grep -v -x -f f2 f1 dovrebbe fare il trucco.

Spiegazione:

  • -v per selezionare le righe non corrispondenti
  • -x per abbinare solo intere righe
  • -f f2 per ottenere modelli da f2

Si può invece utilizzare grep -Fo fgrepper abbinare stringhe fisse da f2piuttosto che i modelli (nel caso in cui si desidera rimuovere le linee in maniera "ciò che vedi, se quello che si ottiene", piuttosto che trattare le linee f2come i modelli regex).


22
Questo ha una complessità O (n²) e inizierà a richiedere ore per essere completato una volta che i file contengono più di poche K righe.
Arnaud Le Blanc

11
Capire quali algoritmi suggeriti da SO hanno complessità O (n ^ 2) ha solo complessità O (n), ma può ancora richiedere ore per competere.
HDave il

2
L'ho appena provato su 2 file di ~ 2k linee ciascuno, ed è stato ucciso dal sistema operativo (concesso, questa è una VM non così potente, ma comunque).
Trebor Rude

1
Amo l'eleganza di questo; Preferisco la velocità della risposta di Jona Christopher Sahnwal.
Alex Hall

1
@ arnaud576875: Sei sicuro? Dipende dall'implementazione di grep. Se viene preelaborato f2correttamente prima di avviare la ricerca, la ricerca richiederà solo O (n) tempo.
Ciao Arrivederci

57

Prova invece la comunicazione (supponendo che f1 e f2 siano "già ordinati")

comm -2 -3 f1 f2

5
Non sono sicuro che commla soluzione abbia la domanda non indica che le linee f1sono ordinate, il che è un prerequisito da usarecomm
gabuzo

1
Questo ha funzionato per me, poiché i miei file erano ordinati e avevano più di 250.000 righe in uno, solo 28.000 nell'altro. Grazie!
Inverno

1
Quando funziona (i file di input vengono ordinati), è estremamente veloce!
Mike Jarvis

Come nella soluzione di arnaud576875, per me che utilizzavo cygwin, questo ha eliminato le righe duplicate nel secondo file che potrebbero voler essere mantenute.
Alex Hall

9
Puoi utilizzare la sostituzione del processo per ordinare prima i file, ovviamente:comm -2 -3 <(sort f1) <(sort f2)
davemyron

14

Per escludere file che non sono troppo grandi, puoi utilizzare gli array associativi di AWK.

awk 'NR == FNR { list[tolower($0)]=1; next } { if (! list[tolower($0)]) print }' exclude-these.txt from-this.txt 

L'output sarà nello stesso ordine del file "from-this.txt". La tolower()funzione fa distinzione tra maiuscole e minuscole, se necessario.

La complessità algoritmica sarà probabilmente O (n) (exclude-these.txt size) + O (n) (from-this.txt size)


Perché dici file che non sono troppo grandi? La paura qui è (presumo) awk che esegue il sistema dalla memoria di sistema per creare l'hash, o c'è qualche altra limitazione?
Rogerdpack

per i follower, ci sono anche altre opzioni più aggressive per "disinfettare" le linee (dato che il confronto deve essere esatto per usare l'array associativo), ex unix.stackexchange.com/a/145132/8337
rogerdpack

@rogerdpack: un file di esclusione di grandi dimensioni richiederà un array hash di grandi dimensioni (e un tempo di elaborazione lungo). Un grande "from-this.txt" richiederà solo un lungo tempo di elaborazione.
In pausa fino a nuovo avviso.

1
Questo fallisce (cioè non produce alcun output) se exclude-these.txtè vuoto. La risposta di @ jona-christopher-sahnwaldt qui sotto funziona in questo caso. Puoi anche specificare più file, ad esempioawk '{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

Simile alla risposta di Dennis Williamson (per lo più modifiche sintattiche, ad esempio l'impostazione del numero di file esplicitamente invece del NR == FNRtrucco):

awk '{if (f==1) { r[$0] } else if (! ($0 in r)) { print $0 } } ' f=1 exclude-these.txt f=2 from-this.txt

L'accesso r[$0]crea la voce per quella riga, non è necessario impostare un valore.

Supponendo che awk utilizzi una tabella hash con ricerca costante e (in media) tempo di aggiornamento costante, la complessità temporale di questa sarà O (n + m), dove n e m sono le lunghezze dei file. Nel mio caso, n era ~ 25 milioni em ~ 14000. La soluzione awk è stata molto più veloce dell'ordinamento e ho anche preferito mantenere l'ordine originale.


In che modo questo differisce dalla risposta di Dennis Williamson? L'unica differenza è che non esegue un'assegnazione nell'hash, quindi leggermente più veloce di questo? La complessità algoritmica è la stessa della sua?
rogerdpack

La differenza è principalmente sintattica. Trovo la variabile fpiù chiara di NR == FNR, ma è una questione di gusti. L'assegnazione nell'hash dovrebbe essere così veloce che non c'è differenza di velocità misurabile tra le due versioni. Penso di essermi sbagliato sulla complessità: se la ricerca è costante, anche l'aggiornamento dovrebbe essere costante (in media). Non so perché ho pensato che l'aggiornamento sarebbe stato logaritmico. Modificherò la mia risposta.
jcsahnwaldt Ripristina Monica il

Ho provato un sacco di queste risposte, e questa è stata veloce da AMAZEBALLS. Avevo file con centinaia di migliaia di righe. Ha funzionato come un fascino!
Mr. T

1
Questa è la mia soluzione preferita. Funziona con più file e anche file di esclusione vuoti, ad es 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. Mentre l'altra awksoluzione fallisce con un file di esclusione vuoto e può prenderne solo uno.
Graham Russell,

5

se hai 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

Che ha complessità O (N ^ 2). Se vuoi preoccuparti delle prestazioni, ecco un'altra versione

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

che utilizza un hash per effettuare la sottrazione, così è la complessità O (n) (dimensione di a) + O (n) (dimensione di b)

ecco un piccolo benchmark, per gentile concessione di user576875, ma con 100K linee, di cui sopra:

$ 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 è stato utilizzato per mostrare che non ci sono differenze tra i 2 file generati.


1
Questo ha una complessità O (n²) e inizierà a richiedere ore per essere completato una volta che i file contengono più di poche K righe.
Arnaud Le Blanc

Non mi interessa davvero in questo frangente, perché non ha menzionato nessun file di grandi dimensioni.
kurumi

3
Non c'è bisogno di essere così difensivi, non è come se @ user576875 avesse svalutato la tua risposta o altro. :-)
John Parker

seconda versione molto bella, vince ruby ​​:)
Arnaud Le Blanc

4

Alcuni confronti temporali tra varie altre risposte:

$ 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 non è nemmeno una differenza simmetrica, perché rimuove le linee che appaiono più volte in entrambi i file.

comm può essere utilizzato anche con le stringhe stdin e here:

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

2

Sembra essere un lavoro adatto per la 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

1

Hai provato questo 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

0

Non una risposta di "programmazione", ma ecco una soluzione rapida e sporca: basta andare su http://www.listdiff.com/compare-2-lists-difference-tool .

Ovviamente non funzionerà per file di grandi dimensioni, ma ha funzionato per me. Alcune note:

  • Non sono affiliato in alcun modo al sito web (se ancora non mi credi, puoi semplicemente cercare uno strumento diverso online; ho usato il termine di ricerca "imposta elenco differenze online")
  • Il sito Web collegato sembra effettuare chiamate di rete su ogni confronto di elenchi, quindi non fornire dati sensibili
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.