Come rimuovere le righe che appaiono sul file B da un altro file A?


160

Ho un grande file A (composto da e-mail), una riga per ogni posta. Ho anche un altro file B che contiene un altro set di mail.

Quale comando dovrei usare per rimuovere tutti gli indirizzi che appaiono nel file B dal file A.

Quindi, se il file A contenesse:

A
B
C

e il file B conteneva:

B    
D
E

Quindi il file A dovrebbe essere lasciato con:

A
C

Ora so che questa è una domanda che potrebbe essere stata posta più spesso, ma ho trovato solo un comando online che mi ha dato un errore con un delimitatore errato.

Qualsiasi aiuto sarebbe molto apprezzato! Qualcuno produrrà sicuramente una fodera intelligente, ma io non sono l'esperto di shell.



1
La maggior parte se le risposte qui sono per i file ordinati e manca quello più ovvio, che ovviamente non è colpa tua, ma che rende l'altro più generalmente utile.
Tripleee

Risposte:


204

Se i file sono ordinati (sono nel tuo esempio):

comm -23 file1 file2

-23elimina le righe che si trovano in entrambi i file o solo nel file 2. Se i file non sono ordinati, esegui il pipe sortprima ...

Vedi la pagina man qui


8
comm -23 file1 file2 > file3produrrà i contenuti in file1 non in file2, in file3. E mv file3 file1infine eliminerebbe i contenuti ridondanti in file1.
Spectral,

2
In alternativa, utilizzare comm -23 file1 file2 | sponge file1. Nessuna pulizia necessaria.
Socowi,

Il link alla pagina man non si sta caricando per me - alternativa: linux.die.net/man/1/comm
Felix Rabe

@Socowi Cos'è la spugna? Non ce l'ho sul mio sistema. (macos 10.13)
Felix Rabe,

@FelixRabe, beh, è ​​noioso. Sostituito con il tuo link. Grazie
L'archetipo Paolo il

85

grep -Fvxf <lines-to-remove> <all-lines>

  • funziona su file non ordinati
  • mantiene l'ordine
  • è POSIX

Esempio:

cat <<EOF > A
b
1
a
0
01
b
1
EOF

cat <<EOF > B
0
1
EOF

grep -Fvxf B A

Produzione:

b
a
01
b

Spiegazione:

  • -F: usa le stringhe letterali anziché il BRE predefinito
  • -x: considera solo le corrispondenze che corrispondono all'intera riga
  • -v: stampa non corrispondente
  • -f file: prende i modelli dal file dato

Questo metodo è più lento su file preordinati rispetto ad altri metodi, poiché è più generale. Se anche la velocità è importante, vedi: Modo rapido per trovare linee in un file che non sono in un altro?

Ecco una rapida automazione bash per il funzionamento in linea:

remove-lines() (
  remove_lines="$1"
  all_lines="$2"
  tmp_file="$(mktemp)"
  grep -Fvxf "$remove_lines" "$all_lines" > "$tmp_file"
  mv "$tmp_file" "$all_lines"
)

GitHub a monte .

utilizzo:

remove-lines lines-to-remove remove-from-this-file

Vedi anche: /unix/28158/is-there-a-tool-to-get-the-lines-in-one-file-that-are-not-in-another


55

imbarazzato in soccorso!

Questa soluzione non richiede input ordinati. Devi prima fornire fileB.

awk 'NR==FNR{a[$0];next} !($0 in a)' fileB fileA

ritorna

A
C

Come funziona?

NR==FNR{a[$0];next} idioma è per memorizzare il primo file in un array associativo come chiavi per un successivo test "contiene".

NR==FNR sta verificando se stiamo eseguendo la scansione del primo file, in cui il contatore della linea globale (NR) è uguale al contatore della linea di file corrente (FNR).

a[$0] aggiunge la riga corrente all'array associativo come chiave, nota che si comporta come un set, dove non ci saranno valori duplicati (chiavi)

!($0 in a)siamo ora nei file successivi, inè un test di contenimento, qui sta verificando se la riga corrente è nell'insieme che abbiamo popolato nel primo passaggio dal primo file, !annulla la condizione. Ciò che manca qui è l'azione, che per impostazione predefinita è {print}e di solito non è scritta in modo esplicito.

Nota che ora può essere utilizzato per rimuovere le parole nella lista nera.

$ awk '...' badwords allwords > goodwords

con una leggera modifica può pulire più liste e creare versioni pulite.

$ awk 'NR==FNR{a[$0];next} !($0 in a){print > FILENAME".clean"}' bad file1 file2 file3 ...

il massimo dei voti su questo. Per usarlo sulla riga di comando in GnuWin32 in Windows, sostituire i singoli stuzzichini con virgolette doppie. funziona a meraviglia. grazie molto.
giorni

Funziona ma come sarò in grado di reindirizzare l'output su fileA sotto forma di A (con una nuova riga) B
Anand Builders

Immagino che intendi A\nC, scrivi prima in un file temporaneo e sovrascrivi il file originale... > tmp && mv tmp fileA
karakfa,

Pieni voti anche da parte mia. Questo awk impiega tutto 1 secondo per elaborare un file con 104.000 voci: +1:
MitchellK

Quando lo si utilizza negli script, assicurarsi innanzitutto che fileBnon sia vuoto (lunghezza 0 byte), poiché in tal caso si otterrà un risultato vuoto anziché il contenuto previsto di fileA. (Causa: FNR==NRsi applicherà a fileAallora.)
Peter Nowee,

18

Un altro modo di fare la stessa cosa (richiede anche input ordinati):

join -v 1 fileA fileB

In Bash, se i file non sono preordinati:

join -v 1 <(sort fileA) <(sort fileB)

7

Puoi farlo a meno che i tuoi file non siano ordinati

diff file-a file-b --new-line-format="" --old-line-format="%L" --unchanged-line-format="" > file-a

--new-line-formatè per le righe che si trovano nel file b ma non in a --old-..è per le righe che si trovano nel file a ma non in b --unchanged-..è per le righe che si trovano in entrambi. %Llo fa in modo che la linea sia stampata esattamente.

man diff

per ulteriori dettagli


1
Dici che funzionerà a meno che i file non siano ordinati. Quali problemi si verificano se vengono ordinati? Cosa succede se sono parzialmente ordinati?
Carlos Macasaet,

1
Ciò era in risposta alla soluzione sopra che suggeriva l'uso del commcomando. commrichiede che i file siano ordinati, quindi se sono ordinati puoi usare anche quella soluzione. Puoi usare questa soluzione indipendentemente dal fatto che il file sia ordinato o meno
aec

7

Questo perfezionamento della bella risposta di @ karakfa potrebbe essere notevolmente più veloce per file molto grandi. Come per quella risposta, nessuno dei file deve essere ordinato, ma la velocità è assicurata in virtù degli array associativi di awk. In memoria è conservato solo il file di ricerca.

Questa formulazione consente anche la possibilità che nel confronto venga utilizzato solo un campo particolare ($ N) nel file di input.

# Print lines in the input unless the value in column $N
# appears in a lookup file, $LOOKUP;
# if $N is 0, then the entire line is used for comparison.

awk -v N=$N -v lookup="$LOOKUP" '
  BEGIN { while ( getline < lookup ) { dictionary[$0]=$0 } }
  !($N in dictionary) {print}'

(Un altro vantaggio di questo approccio è che è facile modificare il criterio di confronto, ad esempio per tagliare lo spazio bianco iniziale e finale).


Questo è più difficile da usare in uno scenario multipiattaforma ad angolo rispetto all'altro rivestimento. Tuttavia, si toglie il cappello per lo sforzo prestazionale
duebob il

2

Puoi usare Python:

python -c '
lines_to_remove = set()
with open("file B", "r") as f:
    for line in f.readlines():
        lines_to_remove.add(line.strip())

with open("file A", "r") as f:
    for line in [line.strip() for line in f.readlines()]:
        if line not in lines_to_remove:
            print(line)
'

2

Puoi usare - diff fileA fileB | grep "^>" | cut -c3- > fileA

Questo funzionerà anche per i file che non sono ordinati.


-1

Per rimuovere le linee comuni tra due file puoi usare il comando grep, comm o join.

grep funziona solo per file di piccole dimensioni. Usa -v insieme a -f.

grep -vf file2 file1 

Ciò visualizza le righe da file1 che non corrispondono a nessuna riga in file2.

comm è un comando di utilità che funziona su file ordinati in modo lessicale. Prende due file come input e produce tre colonne di testo come output: righe solo nel primo file; righe solo nel secondo file; e linee in entrambi i file. Puoi sopprimere la stampa di qualsiasi colonna usando l'opzione -1, -2 o -3 di conseguenza.

comm -1 -3 file2 file1

Ciò visualizza le righe da file1 che non corrispondono a nessuna riga in file2.

Infine, c'è join, un comando di utilità che esegue un join di uguaglianza sui file specificati. La sua opzione -v consente anche di rimuovere le linee comuni tra due file.

join -v1 -v2 file1 file2

Tutti questi erano già stati dati in altre risposte. Il tuo grep ha bisogno di una -F, o otterrai risultati strani quando le linee sembrano regexps
The Archetypal Paul
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.