Eu tenho dois arquivos grandes (conjuntos de nomes de arquivos). Aproximadamente 30.000 linhas em cada arquivo. Estou tentando encontrar uma maneira rápida de encontrar linhas no arquivo1 que não estão presentes no arquivo2.
Por exemplo, se esse for o arquivo1:
line1
line2
line3
E este é o arquivo2:
line1
line4
line5
Então meu resultado / saída deve ser:
line2
line3
Isso funciona:
grep -v -f file2 file1
Mas é muito, muito lento quando usado em meus arquivos grandes.
Eu suspeito que existe uma boa maneira de fazer isso usando diff (), mas a saída deve ser apenas as linhas, nada mais, e não consigo encontrar uma opção para isso.
Alguém pode me ajudar a encontrar uma maneira rápida de fazer isso, usando binários básicos e básicos do linux?
Edição: Para acompanhar a minha própria pergunta, esta é a melhor maneira que eu encontrei até agora usando diff ():
diff file2 file1 | grep '^>' | sed 's/^>\ //'
Certamente deve haver uma maneira melhor?
awk 'NR==FNR{a[$0];next}!($0 in a)' file2 file1 > out.txt
cat file1 file2 file2 | sort | uniq --unique
veja minha resposta abaixo.Respostas:
Você pode conseguir isso controlando a formatação das linhas antigas / novas / inalteradas na
diff
saída GNU :Os arquivos de entrada devem ser classificados para que isso funcione. Com
bash
(ezsh
), você pode classificar no local com a substituição do processo<( )
: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 comodiff
formata as diferenças, semelhantes aosprintf
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:
O
%L
especificador é a linha em questão, e prefixamos cada um com "+" "-" ou "", comodiff -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õescomm
ejoin
) produz apenas a saída esperada com entrada classificada , embora você possa usar<(sort ...)
para classificar no local. Aqui está umawk
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.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 linhass2[]
. Após a leitura dos dois arquivos, repitall1
e use oin
operador para determinar se a linha no arquivo1 está presente no arquivo2. (Isso terá uma saída diferente para odiff
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.
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 linhass1[]
. Então, como o arquivo2 é lido, cada linha correspondente é excluída dell1[]
ess1[]
. 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:Observe o uso e o posicionamento do
-
significadostdin
nagawk
linha de comando. Isso é fornecido pelosplit
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 / BSDsplit
em vez de uma versão GNU.fonte
diff
: em geral, os arquivos de entrada serão diferentes, 1 é retornadodiff
nesse caso. Considere isso um bônus ;-) Se você estiver testando em um shell script 0 e 1 são códigos de saída esperados, 2 indica um problema.man diff
. Obrigado!O comando comm (abreviação de "common") pode ser útil
comm - compare two sorted files line by line
O
man
arquivo é realmente bastante legível para isso.fonte
comm
também tem uma opção para verificar se a entrada está classificada--check-order
(o que parece acontecer de qualquer maneira, mas essa opção causará erros ao invés de continuar). Mas para classificar os arquivos, basta fazer:com -23 <(sort file1) <(sort file2)
e assim por diantecomm
não estava funcionando. Demorei um pouco para descobrir que se trata das terminações de linha: mesmo as linhas que parecem idênticas são consideradas diferentes se tiverem terminações de linha diferentes. O comandodos2unix
pode ser usado para converter as terminações da linha CRLF apenas em LF.Como o konsolebox sugeriu, a solução grep para pôsteres
realmente funciona muito bem (rápido) se você simplesmente adicionar a
-F
opção, para tratar os padrões como cadeias de caracteres fixas em vez de expressões regulares. Eu verifiquei isso em um par de ~ 1000 listas de arquivos de linha que tive que comparar. Com-F
isso, levou 0,031 s (real), enquanto sem levou com 2.278 s (real), ao redirecionar a saída grep parawc -l
.Esses testes também incluíram a
-x
opção, que é parte necessária da solução para garantir total precisão nos casos em que o arquivo2 contém linhas que correspondem a parte de, mas não todas, uma ou mais linhas do arquivo1.Portanto, uma solução que não exige que as entradas sejam classificadas é rápida e flexível (diferenciação de maiúsculas e minúsculas, etc.) é:
Isso não funciona com todas as versões do grep, por exemplo, falha no macOS, onde uma linha no arquivo 1 será mostrada como não presente no arquivo 2, mesmo que seja, se corresponder a outra linha que é uma substring dele . Como alternativa, você pode instalar o GNU grep no macOS para usar esta solução.
fonte
-F
isso não escala bem.file2
.-x
opção aparentemente usa mais memória. Comfile2
180 milhões de palavras contendo 6 a 10 bytes, meu processo chegouKilled
a uma máquina com 32 GB de RAM ...qual é a velocidade de como classificar e diff?
fonte
Se você está com falta de "ferramentas sofisticadas", por exemplo, em alguma distribuição mínima do Linux, há uma solução com just
cat
,sort
euniq
:Teste:
Isso também é relativamente rápido, comparado a
grep
.fonte
--unique
opção. Você deve ser capaz de usar a opção POSIX padronizado para isso:| uniq -u
seq 1 1 7
cria números de 1, com incremento 1, até 7, ou seja, 1 2 3 4 5 6 7. E aí está o seu 2!O
-t
garante que ele compara a linha inteira, se você tiver um espaço em algumas das linhas.fonte
comm
,join
exige que as duas linhas de entrada sejam classificadas no campo em que você está executando a operação de junção.Você pode usar o Python:
fonte
Use
combine
demoreutils
pacote, um utilitário conjuntos que suportanot
,and
,or
,xor
operaçõesou seja, me dê linhas que estão no arquivo1, mas não no arquivo2
OU me dê linhas no arquivo1 menos linhas no arquivo2
Nota:
combine
classifica e localiza linhas exclusivas nos dois arquivos antes de executar qualquer operação, masdiff
não o faz. Então você pode encontrar diferenças entre a saída dediff
ecombine
.Então, com efeito, você está dizendo
Encontre linhas distintas em arquivo1 e arquivo2 e, em seguida, me dê linhas em arquivo1 menos linhas em arquivo2
Na minha experiência, é muito mais rápido que outras opções
fonte
Usar o fgrep ou adicionar a opção -F ao grep pode ajudar. Mas, para cálculos mais rápidos, você pode usar o Awk.
Você pode tentar um destes métodos do Awk:
http://www.linuxquestions.org/questions/programming-9/grep-for-huge-files-826030/#post4066219
fonte
A maneira como costumo fazer isso é usar a
--suppress-common-lines
bandeira, embora observe que isso só funciona se você fizer isso no formato lado a lado.diff -y --suppress-common-lines file1.txt file2.txt
fonte
Eu descobri que, para mim, usar uma instrução if e for normal funcionou perfeitamente.
fonte
grep
resultados se expandir para várias palavras ou se alguma das suasfile2
entradas puder ser tratada pelo shell como um globo.