Verifique se todas as linhas do arquivo ocorrem em arquivos diferentes


14

Eu tenho dois arquivos: arquivo1 com cerca de 10.000 linhas e arquivo2 com algumas centenas de linhas. Quero verificar se todas as linhas do arquivo2 ocorrem no arquivo1. Ou seja: ∀ linha ∈ arquivo2: ∈ ∈ arquivo1

Se alguém não souber o que esses símbolos significam ou o que "verificar se todas as linhas do arquivo2 ocorrem no arquivo1" significa: Várias linhas equivalentes nos dois arquivos não influenciam se a verificação retorna que os arquivos atendem ou não ao requisito.

Como eu faço isso?


2
Esses arquivos podem ter linhas duplicadas? Se file2contém 2 linhas A, você precisa file1conter pelo menos 2 linhas A?
Stéphane Chazelas

2
@ StéphaneChazelas Todas as linhas (em ambos os arquivos) são garantidas como únicas.
UTF-8

1
@ UTF-8 Esse seria um detalhe importante para editar na sua pergunta.
David Z

2
@DavidZ Não é mais porque as respostas existentes não contam com essa garantia. Então, editando a pergunta agora, reduziria o escopo aparente das respostas.
UTF-8

@ UTF-8 Suponho que sim, embora a pergunta seja um pouco ambígua sem ela, por exemplo, se uma determinada linha ocorrer 5 vezes no arquivo2, essa linha também precisará ocorrer 5 vezes no arquivo1 (em vez de apenas uma vez)? Se você tivesse esse requisito, não parece que nenhuma das respostas existentes funcionaria; portanto, sugiro pelo menos editar algo que deixe claro que não é isso que você quer dizer.
David Z

Respostas:


18
comm -13 <(sort -u file_1) <(sort -u file_2)

Este comando produzirá linhas exclusivas para file_2. Portanto, se a saída estiver vazia, todas as file_2linhas estarão contidas no file_1.

Do homem do comm:

   With  no  options,  produce  three-column  output.  Column one contains
   lines unique to FILE1, column two contains lines unique to  FILE2,  and
   column three contains lines common to both files.

   -1     suppress column 1 (lines unique to FILE1)

   -2     suppress column 2 (lines unique to FILE2)

   -3     suppress column 3 (lines that appear in both files)

@don_crissti True. Corrigido: a -uopção adicionada ao sortcomando. Agora, apenas linhas únicas são deixadas nos dois arquivos classificados.
MiniMax

Impressionantemente simplesmente solução! Essa sintaxe é aplicável a qualquer programa que espera arquivos? Eu sempre pensei que o <canal estava em stdin. O termo entre colchetes muda isso?
UTF-8

2
@ UTF-8 É chamado de substituição de processo . Você pode ler aqui sobre isso. E sim, ele se comporta como um arquivo temporário, portanto pode ser usado em vez de arquivos reais em qualquer programa, o que espera arquivos.
MiniMax

Se isso é algo que você faz com frequência, convém armazenar file_1na forma pré-definida. Economiza digitação e tempo.
Stig Hemmer

7
Bom comentário, exceto o "qualquer". A substituição do processo, embora maravilhosa, não pode ser usada em todos os casos, porque os "arquivos" resultantes são fluxos e não arquivos reais. Isso significa que eles não são "procuráveis" como um arquivo normal e só podem ser usados ​​quando o programa lê o arquivo normalmente desde o início, e não quando o programa usa alguma funcionalidade somente de arquivo, como procurar um ponto específico ou rebobinar para recomeçar desde o início. Felizmente, a maioria dos programas simplesmente lê () seus arquivos e, portanto, a substituição de processos funciona com a maioria dos programas, mas não com "nenhum" programa.
Law29

7
[ $(grep -cxFf file2 <(sort -u file1)) = $(sort -u file2 | wc -l) ] && 
  echo all there || 
  echo some missing

Se o número de correspondências do arquivo2 no (as linhas exclusivas de) arquivo1 for igual ao número de linhas exclusivas no arquivo2, elas estarão todas lá; caso contrário, eles não são.


5

Usar o GNU awkonde ele suporta length(array)recursos específicos (e alguma outra awkimplementação que possa suportar) e não é necessário se os arquivos forem classificados.

gawk 'FNR==NR{seen[$0];next} ($0 in seen){delete seen[$0]};
    END{print (!length(seen))?"Matched":"Not Matched"}' file2 file1

Isso está lendo o arquivo2 em uma matriz chamada seencom a chave como linha inteira do arquivo2 .

Em seguida, leia o arquivo1 e, para cada linha, se corresponder às linhas da matriz vista, exclua essa chave.

No final, se a matriz estava vazia, todas as linhas do arquivo2 existem no arquivo1 e serão impressas Matched, caso contrário, serão exibidas Not Matched.


Para a compatibilidade em todas as awkimplementações.

awk 'FNR==NR{seen[$0];next} ($0 in seen){delete seen[$0]};
    END{for(x in seen);print (!x)?"Matched":"Not Matched"}' file2 file1

Para ignorar linhas vazias / ou linhas com espaços em branco somente se no arquivo2 , você precisará adicionar NFà condição NR==FNR && NF {...para pular a leitura delas na matriz.


length(array)é apenas um AFAIK; definitivamente não é POSIX.
Dave_thompson_085

@ dave_thompson_085 Correto, atualizei minha resposta. obrigado
α Octsнιη

3

Usando commvocê pode encontrar linhas que são comuns nos dois arquivos.

comm -12 file1 file2

Dê uma olhada man commpara mais detalhes


Correto, está retornando linhas comuns nos dois arquivos, mas isso não fornece uma resposta para o Q do OP, onde se você tivesse uma linha no arquivo2 que não sai no arquivo1, então todas as linhas do arquivo2 não existem no arquivo1.
αғsнιη

1
arquivos devem ser classificados. From man " comm- compare dois arquivos classificados linha por linha".
MiniMax

@MiniMax está certo. Isso não funciona. A outra resposta utilizada commcontém uma solução que não está obviamente incorreta. Quando executo seu comando, recebo avisos de que os arquivos não estão na ordem de classificação e muitas linhas que definitivamente estão nos dois arquivos.
UTF-8

3
diff -q <(sort -u file2) <(grep -Fxf file2 file1 | sort -u)

produzirá nenhuma saída se file1contém todas as linhas em file2e sair com o status 0, caso contrário ele irá imprimir algo como

Files /proc/self/fd/11 and /proc/self/fd/12 differ

e saia com status 1


2

Use um programa Python:

#!/usr/bin/env python3
import sys

def open_arg(path):
    return sys.stdin if path == '-' else open(path)

def strip_linebreak(s):
    return s[:-1] if s.endswith('\n') else s

with open_arg(sys.argv[1]) as pattern_file:
    patterns = set(map(strip_linebreak, pattern_file))

with open_arg(sys.argv[2]) as dataset_file:
    for l in map(strip_linebreak, dataset_file):
        patterns.remove(l)
        if not patterns:
            break

sys.exit(int(bool(patterns)))

Uso:

python3 contains-all.py file2 file1

O status de saída do programa indica se todos os padrões do arquivo 2 foram correspondidos:

  • 0 (sucesso) significa que todos os padrões foram correspondidos.
  • 1 (falha) significa que alguns padrões não foram correspondidos.

Para consultar o status de saída em um shell (roteiro) você pode usar a $?variável especial ou outras expressões que avaliam o status de saída de comando, por exemplo, operadores de curto-circuito &&e ||e expressões condicionais como ifou while. Exemplo:

if python3 compare-all.py file2 file1 && some-other --condition; then
    # do stuff
fi

1

combinede moreutils vai lhe mostrar todas as linhas em file2que não estão em file1com:

combine file2 not file1

Em seguida, você pode contar o número de linhas canalizando-o para wc -l, como:

if [ $(combine file2 not file1 | wc -l) != 0 ]; then
  echo "lines missing"
else
  echo "You're fine"
fi
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.