Você pode conseguir isso controlando a formatação das linhas antigas / novas / inalteradas na diff
saí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 diff
opções que outras soluções não oferecem, tais como -i
para ignorar caso, ou várias opções em branco ( -E
, -b
, -v
etc.) para correspondência menos rigoroso.
Explicação
As opções --new-line-format
, --old-line-format
e --unchanged-line-format
permitem controlar a forma como diff
formata as diferenças, semelhantes aos printf
especificadores 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 %L
especificador é 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 diff
método (junto com outras sugestões comm
e join
) produz apenas a saída esperada com entrada classificada , embora você possa usar <(sort ...)
para classificar no local. Aqui está um awk
script 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 ll1
e use o in
operador para determinar se a linha no arquivo1 está presente no arquivo2. (Isso terá uma saída diferente para o diff
mé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 stdin
na gawk
linha de comando. Isso é fornecido pelo split
arquivo1 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 split
em vez de uma versão GNU.
awk 'NR==FNR{a[$0];next}!($0 in a)' file2 file1 > out.txt