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.