Mostra apenas as linhas que estão em todos os arquivos de texto pelo menos uma vez

4

a.txt

cat a.txt
a
b
x
c

b.txt

cat b.txt
d
e
a
f

o q:

SOMEMAGICK *.txt
a

Q: como posso mostrar apenas as linhas que estão em todos os arquivos * .txt?

    
por gasko peter 05.09.2013 / 20:17

6 respostas

4

Que tal

cat *.txt | sort | uniq -c | egrep "^ +$(ls -1 *.txt | wc -l) "

E então, para obter o número de aparências retiradas, você pode adicionar ...

cat *.txt | sort | uniq -c | egrep "^ +$(ls -1 *.txt | wc -l) " | sed -re 's/^ +[0-9]+ //'

De acordo com o comentário de @Stephane, o acima não funcionará se uma linha aparecer várias vezes em um único arquivo. Aqui eu classifico e uniq cada arquivo primeiro para evitar isso:

for f in *.txt; do sort -u $f > $f.uniqd; done
cat *.uniqd | sort | uniq -c | egrep "^ +$(ls -1 *.uniqd | wc -l) " | sed -re 's/^ +[0-9]+ //'

Embora agora não seja mais uma linha de ação. :)

    
por 05.09.2013 / 20:41
4
awk 'FNR == 1 { FILENUM++ }
     SEEN[$0] == FILENUM - 1 { SEEN[$0] = FILENUM }
     END { for (s in SEEN) if (FILENUM == SEEN[s]) print s }' *.txt

Explicação

Ao ler a primeira linha de cada arquivo, incremente FILENUM , de modo que ao ler o arquivo n th, FILENUM seja n .

Ao ler cada linha, conte o número de arquivos em que aquela linha foi vista (mas você só precisa se preocupar em fazer isso se a linha tiver sido vista em todos os arquivos anteriores).

Quando não houver mais entrada para ler, imprima todas as linhas que foram vistas em todos os arquivos.

Cuidado: Como com várias das soluções postadas aqui, esta também tem uma fraqueza. De acordo com a pergunta, se algum dos arquivos de entrada estiver vazio, supõe-se que não exista nenhuma saída . No entanto, como o awk é uma ferramenta orientada a linhas, ele ignora arquivos vazios. Ou seja, o FNR == 1 { FILENUM++ } falha ao incrementar FILENUM para arquivos vazios.

Com o GNU awk, é possível corrigir esse bug usando o ARGIND variável incorporada.

gawk 'SEEN[$0] == ARGIND - 1 { SEEN[$0] = ARGIND }
      END { for (s in SEEN) if (ARGIND == SEEN[s]) print s }' *.txt
    
por 06.09.2013 / 10:25
3

Usando o GNU awk

awk '{
      x[$0][FILENAME]
     }
     END{
      num_files=ARGC-1;
      for (b in x)
       if (length(x[b]) == num_files) 
        print b
     }' a.txt b.txt c.txt
    
por 05.09.2013 / 20:37
2

Você poderia fazer:

export LC_ALL=C
sort -u a.txt |
  comm -12 - <(sort -u b.txt) |
  comm -12 - <(sort -u c.txt) |
  comm -12 - <(sort -u d.txt)

O que seria relativamente eficiente, mas não é fácil estendê-lo a um número arbitrário de arquivos.

    
por 07.09.2013 / 10:37
1

Eu gosto de uma solução mais fácil usando join :

join <(sort a.txt) <(sort b.txt)

Isso funciona em seus dois arquivos de entrada, mas pode não se comportar como esperado nas linhas que contêm espaços, mas também gerará linhas duplicadas várias vezes.

Para remediar o segundo problema, apenas

join <(sort a.txt) <(sort b.txt) | uniq

O primeiro é um pouco mais complicado, mas eu trapaceei um pouco com o -t flag, para usar um caracter não recorrente como separador de campo:

$ cat a.txt 
This test
foo bar
does work
$ cat b.txt 
This is a test
foo does not work
does work
$ join <(sort a.txt) <(sort b.txt) | uniq
does work work
foo bar does not work
This test is a test
$ join -t : <(sort a.txt) <(sort b.txt) | uniq
does work
    
por 06.09.2013 / 08:35
1

Para 2 arquivos

Isso não é mais complicado do que usar a capacidade de grep de usar uma lista de palavras. Por exemplo:

$ grep -f b.txt a.txt 

Exemplo

# a.txt
$ cat a.txt 
a
abc defg
de
bcd
xyz bcd
c

# b.txt
$ cat b.txt 
d
e bcd
a
f
bcd

# common lines to a.txt & b.txt
$ grep -Fxf b.txt a.txt
a
bcd

OBSERVAÇÃO: Dependendo dos dados, você pode precisar adicionar um | sort -u após o grep se houver linhas duplicadas nos arquivos!

Detalhes

-F, --fixed-strings
     Interpret PATTERN as a list of fixed strings, separated by newlines, 
     any of which is to be matched.  (-F is specified by POSIX.)

-x, --line-regexp
     Select only those matches that exactly match the whole line.  
     (-x is specified by POSIX.)

-f FILE, --file=FILE
     Obtain patterns from FILE, one per line.  The empty file contains 
     zero patterns, and therefore matches nothing.  (-f is specified by 
     POSIX.)

Por 3 ou mais

Você pode usar o fato de que, se comparar qualquer arquivo aos outros, o que é comum em todos eles em comparação com esse arquivo, todos os arquivos devem compartilhar essa linha comum. Novamente usando grep -f como acima, mas desta vez teremos que percorrer os arquivos usando um loop for .

$ mf=""; for i in *.txt; do [ -z "$mf" ] && mf=$i && continue; grep -Fxf $mf $i;done | sort -u

Se adicionarmos alguns arquivos adicionais ao mix:

# c.txt
$ cat c.txt 
a
z
d bcd
e
q
bcd

# d.txt
$ cat d.txt 
a
z
e
z bcd
bcd

Executar nosso código produz isso:

$ mf=""; for i in *.txt;do [ -z "$mf" ] && mf=$i && continue; grep -Fxf $mf $i;done | sort -u
a
bcd
    
por 05.09.2013 / 20:51

Tags