Puoi farlo controllando la formattazione delle linee vecchie / nuove / invariate diffnell'output GNU :
diff --new-line-format="" --unchanged-line-format="" file1 file2
I file di input devono essere ordinati affinché funzioni. Con bash(e zsh) è possibile ordinare sul posto con la sostituzione del processo <( ):
diff --new-line-format="" --unchanged-line-format="" <(sort file1) <(sort file2)
Nelle righe nuove e invariate sopra vengono eliminate , quindi vengono emesse solo le linee modificate (ovvero le linee rimosse nel tuo caso). Si può anche utilizzare un paio di diffopzioni che altre soluzioni non offrono, come ad esempio -idi ignorare caso, o le varie opzioni di spaziatura ( -E, -b, -vecc) per meno severe di corrispondenza.
Spiegazione
Le opzioni --new-line-format, --old-line-formate --unchanged-line-formatconsentono di controllare il modo in cui diffformatta le differenze, simili a printfidentificatori di formato. Queste opzioni formattano rispettivamente le linee nuove (aggiunte), vecchie (rimosse) e invariate . L'impostazione di uno su "" impedisce l'output di quel tipo di linea.
Se hai familiarità con il formato diff unificato , puoi ricrearlo parzialmente con:
diff --old-line-format="-%L" --unchanged-line-format=" %L" \
--new-line-format="+%L" file1 file2
Lo %Lspecificatore è la riga in questione e ogni prefisso è "+" "-" o "", come diff -u
(si noti che genera solo differenze, manca le righe --- +++e @@nella parte superiore di ogni modifica raggruppata). È inoltre possibile utilizzare questo per fare altre cose utili come numero di ogni riga con %dn.
Il diffmetodo (insieme ad altri suggerimenti comme join) produce solo l'output previsto con input ordinato , sebbene sia possibile utilizzare <(sort ...)per ordinare in posizione. Ecco un semplice awkscript (nawk) (ispirato agli script collegati nella risposta di Konsolebox) che accetta file di input ordinati in modo arbitrario e genera le righe mancanti nell'ordine in cui si verificano nel file1.
# output lines in file1 that are not in file2
BEGIN { FS="" } # preserve whitespace
(NR==FNR) { ll1[FNR]=$0; nl1=FNR; } # file1, index by lineno
(NR!=FNR) { ss2[$0]++; } # file2, index by string
END {
for (ll=1; ll<=nl1; ll++) if (!(ll1[ll] in ss2)) print ll1[ll]
}
Ciò memorizza l'intero contenuto di file1 riga per riga in un array indicizzato numero riga ll1[]e l'intero contenuto di file2 riga per riga in un array associativo indicizzato contenuto riga ss2[]. Dopo aver letto entrambi i file, scorrere ll1e utilizzare l' inoperatore per determinare se la riga in file1 è presente in file2. (Questo avrà un output diverso rispetto al diffmetodo se ci sono duplicati.)
Nel caso in cui i file siano sufficientemente grandi da archiviarli entrambi, si verifica un problema di memoria, è possibile scambiare la CPU con la memoria archiviando solo file1 ed eliminando le corrispondenze lungo la lettura di file2.
BEGIN { FS="" }
(NR==FNR) { # file1, index by lineno and string
ll1[FNR]=$0; ss1[$0]=FNR; nl1=FNR;
}
(NR!=FNR) { # file2
if ($0 in ss1) { delete ll1[ss1[$0]]; delete ss1[$0]; }
}
END {
for (ll=1; ll<=nl1; ll++) if (ll in ll1) print ll1[ll]
}
Quanto sopra memorizza l'intero contenuto di file1 in due array, uno indicizzato per numero di riga ll1[], uno indicizzato per contenuto di riga ss1[]. Quindi, quando viene letto file2, ogni riga corrispondente viene eliminata da ll1[]e ss1[]. Alla fine vengono emesse le righe rimanenti da file1, preservando l'ordine originale.
In questo caso, con il problema come indicato, puoi anche dividere e conquistare usando GNU split(il filtro è un'estensione GNU), ripetute esecuzioni con blocchi di file1 e lettura di file2 ogni volta:
split -l 20000 --filter='gawk -f linesnotin.awk - file2' < file1
Nota l'uso e il posizionamento del -significato stdinsulla gawkriga di comando. Questo è fornito da splitfile1 in blocchi di 20000 righe per invocazione.
Per gli utenti di sistemi non-GNU, non v'è quasi certamente un coreutils GNU pacchetto è possibile ottenere, anche su OSX come parte delle di Apple Xcode strumenti che fornisce GNU diff, awk, anche se solo un POSIX / BSD split, piuttosto che una versione di GNU.
awk 'NR==FNR{a[$0];next}!($0 in a)' file2 file1 > out.txt