grep linhas que existem em um arquivo, mas não no outro

3

Estou tentando fazer o grep e o grep -v simples, portanto, irei obter as linhas de a.txt que existem em b.txt e não em c.txt .

Exemplo de 3 arquivos

a.txt :

a
b
c
d
e

up.txt :

a.up
b.up
c.up

dw.txt :

a.dw
b.dw

Saída desejada:

c

Eu escrevi o código abaixo, mas o grep analisa o $(sed...) como uma única linha de cada vez e não como um todo:

sed 's/.up//' /tmp/b.txt | grep -f /tmp/a.txt | grep -vf $(sed 's/.dw//' /tmp/c.txt)
    
por Nir 08.07.2018 / 15:08

8 respostas

3

Supondo que os arquivos estejam todos ordenados e que estamos usando um shell que entenda as substituições do processo (como bash ):

$ join -t . -v 1 -o 0 <( join -t . a.txt b.txt ) c.txt
c

ou, para outras camadas,

$ join -t . a.txt b.txt | join -t . -v 1 -o 0 - c.txt
c

Isso usa join duas vezes para realizar junções relacionais entre os arquivos. Os dados são interpretados como campos delimitados por pontos (com -t . ).

A junção entre a.txt e b.txt é direta e produz

a.up
b.up
c.up

Estas são todas as linhas dos dois arquivos cujo primeiro campo delimitado por ponto ocorre nos dois arquivos. A saída consiste no campo de junção ( a , b , c ) seguido pelos outros campos de ambos os arquivos (somente b.txt possui dados adicionais).

A segunda junção é um pouco mais especial. Com -v 1 , pedimos para ver as entradas no primeiro arquivo (o resultado intermediário acima) que não podem ser emparelhadas com nenhuma linha no segundo arquivo, c.txt . Além disso, pedimos apenas para ver o próprio campo de associação ( -o 0 ). Sem o -o flag, obteríamos c.up como resultado.

Se os arquivos não forem classificados, cada ocorrência de um nome de arquivo file poderá ser substituída por <( sort file ) no comando.

    
por 08.07.2018 / 15:50
4

Com o comando simples rápido GNU awk :

awk -F'.' \
'{
     if (ARGIND == 1) a[$1];
     else if (ARGIND == 2 && $1 in a) comm[$1];
     else if (ARGIND == 3){
         delete a;
         if ($1 in comm) delete comm[$1]
     }
 }
 END{ for (i in comm) print i }' a.txt b.txt c.txt

A saída:

c
  • -F'.' - trata . como separador de campos
  • ARGIND - O índice em ARGV (matriz de argumentos da linha de comando) do arquivo atual sendo processado
  • comm - matriz de itens comuns entre os primeiros 2 arquivos ( a.txt e b.txt )
por 08.07.2018 / 16:08
2

comm

Supondo que os arquivos estão classificados e as linhas duplicadas foram removidas:

comm -12 a.txt <(cut -d. -f1 b.txt) | comm -23 - <(cut -d. -f1 c.txt)

Isto é escrito para o Ubuntu, usando utilitários Bash e GNU, mas esperamos que funcione para outros sistemas operacionais.

Explicação

  • comm -12 Imprime as linhas que ambos os arquivos compartilham (leia man comm para detalhes)
  • <(...) Substituição do processo - Use um comando no lugar de um arquivo de entrada
  • cut -d. -f1 Para cada linha, remova tudo após o primeiro ponto
  • comm -23 Imprimir as linhas exclusivas do primeiro arquivo
  • - Ler de stdin em vez de um arquivo
por 08.07.2018 / 23:07
2

Se os arquivos fornecidos forem classificados e não houver duplicatas internas, use isto:

$ comm -12 a.txt <(sed 's/\.[^.]*$//' up.txt) | comm -23 - <(sed 's/\.[^.]*$//' dw.txt)

Em shells que têm substituição de processo ( <(…) ). Para outras conchas, leia abaixo.

O que você descreve nesta frase:

get the lines from a.txt that exists in b.txt and not in c.txt

pode ser reduzido para as operações definidas:

( a intersect b ) complement c

Existem várias maneiras de executar operações em arquivos, e muitos estão listados nesta resposta

Eu gosto da maneira como o comando comm poderia realizar a maioria das operações.
Mas os arquivos que você apresenta não são o conjunto limpo a ser usado. As extensões precisam ser apagadas / removidas. A maneira genérica de remover as extensões com sed é:

$ sed 's/\.[^.]*$//' file

Assim, os dois arquivos limpos serão criados com:

$ sed 's/\.[^.]*$//' up.txt > up.txt.clean
$ sed 's/\.[^.]*$//' dw.txt > dw.txt.clean

Com esses dois arquivos, uma solução de uma linha é:

$ comm -12 a.txt up.txt.clean | comm -23 - dw.txt.clean
c

Ou fazendo ( up.txt complement dw.txt) intersect a.txt :

$ comm -23 up.txt.clean dw.txt.clean | comm -12 - a.txt
c

Ambos os comandos podem ser implementados diretamente de arquivos originais em alguns shells com:

$ comm -12 a.txt <(sed 's/\.[^.]*$//' up.txt) | comm -23 - <(sed 's/\.[^.]*$//' dw.txt)

Se a substituição do processo não estiver disponível, é possível usar apenas um arquivo da seguinte forma:

$ sed 's/\.[^.]*$//' up.txt | comm -12 a.txt - >result1.txt
$ sed 's/\.[^.]*$//' dw.txt | comm -23 result1.txt -
c
$ rm result1.txt
    
por 09.07.2018 / 03:15
0

Aqui está outra alternativa usando o mesmo que o seu usando: grep , sort & uniq e sed .

$ sed 's/\.\(dw\|up\)//' up.txt dw.txt | grep -xFf a.txt | sort | uniq -u
c

Isso funciona produzindo uma lista de correspondências para cada um dos arquivos up.txt e dw.txt usando a.txt como um arquivo de entrada para grep . Isso produz saída assim:

$ sed 's/\.\(dw\|up\)//' up.txt dw.txt | grep -xFf a.txt
a
b
c
a
b

Os principais detalhes aqui são:

  • usando sed para cortar as extensões à direita dos dois arquivos up.txt e dw.txt
  • Com extensões excluídas, usamos grep para filtrar correspondências correspondentes de a.txt
  • A correspondência que informamos grep para realizar é exata, -x
  • O -F indica grep para tratar os padrões em a.txt como sequências de caracteres fixas

Com a saída acima, você pode simplesmente executar isso através de sort e, em seguida, usar uniq para obter apenas as linhas que não se repetem.

Referências

por 08.07.2018 / 19:05
0
perl -F\. -lane '
   $h{@ARGV}{$F[0]}++,next if @ARGV;
   print if exists $h{2}{$_} && !exists $h{1}{$_};
' up.txt dw.txt a.txt

Construa o hash% h com as chaves de nível superior como "2" e "1", com 2 referindo-se ao primeiro argumento (up.txt), 1 referindo-se a dw.txt. Para os dados fornecidos, a estrutura hash seria algo como: (a ordem pode ser diferente)

%h = (
   1 => { a => 1, b => 1, },
   2 => { a => 1, b => 1, c => 1, },
);

como pode ser visto, há dois mini-hashes dentro do hash principal% h. Então, quando chega a hora de ler o terceiro argumento (a.txt), tomamos a decisão de imprimir o registro com base em se esse registro pode ser visto (como uma chave) no mini-hash% 2 E não visto no mini-hash% 1, dentro do hash principal% h (também conhecido como hash-of-hashes ou HoH).

    
por 09.07.2018 / 12:52
0

Uma variação da resposta de Roman e para simplificar:

gawk -F. 'ARGIND==1{ seen[$1]; next } 
         ARGIND==2{ delete seen[$1]; next }
         ($1 in seen)
' fileUP fileDW fileA
  • Este ARGIND==1{ seen[$1]; next } contendo a primeira coluna de fileUP em uma matriz associada chamada seen .
  • Este ARGIND==2{ delete seen[$1]; next } exclui aqueles que existem em fileDW .
  • e esse ($1 in seen) imprime permaneceu quando também existe em fileA
por 09.07.2018 / 13:55
0
$ grep -f a.txt <(cut -d '.' -f 1 up.txt) > common.txt
$ grep -vf <(cut -d '.' -f 1 dw.txt) common.txt

Compara a primeira palavra entre dois arquivos e grava a palavra correspondente em common.txt . Compara dw.txt com common.txt e imprime a correspondência invertida, isto é, 'c'.

    
por 09.07.2018 / 11:29