Você pode conseguir isso controlando a formatação das linhas antigas / novas / inalteradas na diffsaída GNU :
diff --new-line-format="" --unchanged-line-format="" file1 file2
Os arquivos de entrada devem ser classificados para que isso funcione. Com bash(e zsh), você pode classificar no local com a substituição do processo <( ):
diff --new-line-format="" --unchanged-line-format="" <(sort file1) <(sort file2)
Nas linhas novas e inalteradas acima , são suprimidas, portanto, apenas as alterações (ou seja, linhas removidas no seu caso) são exibidas. Você também pode usar algumas diffopções que outras soluções não oferecem, tais como -ipara ignorar caso, ou várias opções em branco ( -E, -b, -vetc.) para correspondência menos rigoroso.
Explicação
As opções --new-line-format, --old-line-formate --unchanged-line-formatpermitem controlar a forma como diffformata as diferenças, semelhantes aos printfespecificadores de formato. Essas opções formatam as linhas nova (adicionada), antiga (removida) e inalterada , respectivamente. Definir um como vazio "" impede a saída desse tipo de linha.
Se você estiver familiarizado com o formato diff unificado , é possível recriá-lo parcialmente com:
diff --old-line-format="-%L" --unchanged-line-format=" %L" \
--new-line-format="+%L" file1 file2
O %Lespecificador é a linha em questão, e prefixamos cada um com "+" "-" ou "", como diff -u
(observe que ele apenas gera diferenças, não possui as linhas --- +++e @@na parte superior de cada alteração agrupada). Você também pode usar isso para fazer outras coisas úteis como número cada linha com %dn.
O diffmétodo (junto com outras sugestões comme join) produz apenas a saída esperada com entrada classificada , embora você possa usar <(sort ...)para classificar no local. Aqui está um awkscript simples (nawk) (inspirado nos scripts vinculados à resposta do Konsolebox) que aceita arquivos de entrada ordenados arbitrariamente e gera as linhas ausentes na ordem em que ocorrem no arquivo1.
# 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]
}
Isso armazena todo o conteúdo do arquivo1 linha por linha em uma matriz indexada por número de linha ll1[]e todo o conteúdo do arquivo2 linha por linha em uma matriz associativa indexada por conteúdo de linha ss2[]. Após a leitura dos dois arquivos, repita ll1e use o inoperador para determinar se a linha no arquivo1 está presente no arquivo2. (Isso terá uma saída diferente para o diffmétodo se houver duplicatas.)
No caso de os arquivos serem suficientemente grandes, o armazenamento dos dois causa um problema de memória, você pode trocar a CPU por memória armazenando apenas o arquivo1 e excluindo correspondências ao longo do caminho à medida que o arquivo2 é lido.
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]
}
O exemplo acima armazena todo o conteúdo do arquivo1 em duas matrizes, uma indexada pelo número da linha ll1[], uma indexada pelo conteúdo da linha ss1[]. Então, como o arquivo2 é lido, cada linha correspondente é excluída de ll1[]e ss1[]. No final, as linhas restantes do arquivo1 são exibidas, preservando a ordem original.
Nesse caso, com o problema conforme indicado, você também pode dividir e conquistar usando o GNU split(a filtragem é uma extensão do GNU), execuções repetidas com partes do arquivo1 e lendo o arquivo2 toda vez:
split -l 20000 --filter='gawk -f linesnotin.awk - file2' < file1
Observe o uso e o posicionamento do -significado stdinna gawklinha de comando. Isso é fornecido pelo splitarquivo1 em pedaços de 20000 linhas por chamada.
Para usuários em sistemas não-GNU, há quase certamente um GNU coreutils pacote que você pode obter, incluindo no OSX como parte dos da Apple Xcode ferramentas que fornece GNU diff, awk, embora apenas um POSIX / BSD splitem vez de uma versão GNU.
awk 'NR==FNR{a[$0];next}!($0 in a)' file2 file1 > out.txt