Bash - emparelhe cada linha de arquivo


10

Esta questão está fortemente relacionada a esta e a esta pergunta. Eu tenho um arquivo que contém várias linhas onde cada linha é o caminho para um arquivo. Agora eu quero parear cada linha com cada linha diferente (não ela mesma). Além disso, um par A Bé igual a um B Apar para meus propósitos, portanto apenas uma dessas combinações deve ser produzida.

Exemplo

files.dat lê assim em uma notação abreviada, cada letra é um caminho de arquivo (absoluto ou relativo)

a
b
c
d
e

Então meu resultado deve ser algo como isto:

a b
a c
a d
a e
b c
b d
b e
c d
c e
d e

De preferência, eu gostaria de resolver isso no bash. Diferentemente das outras perguntas, minha lista de arquivos é bastante pequena (cerca de 200 linhas), portanto, usar loops e capacidade de RAM não apresenta problemas.


Ele precisa estar no bash adequado ou apenas algo disponível via linha de comando do bash? Outros utilitários estão melhor posicionados para processar texto.
Jeff Schaller

@JeffSchaller Algo acessível através da linha de comando do bash. Eu estava um pouco incerto, desculpe
Enno

Isso está quase se tornando um Code Golf : P
Richard de Wit

3
Como regra geral, contanto que você precise fazer algo não trivial, use sua linguagem de script favorita sobre o BASH. Será menos frágil (por exemplo, contra caracteres ou espaços especiais) e muito mais fácil de expandir sempre que você precisar (se precisar de três ou filtrar alguns deles). O Python ou Perl deve ser instalado em praticamente qualquer caixa do Linux, portanto, são boas escolhas (a menos que você esteja trabalhando em sistemas embarcados, como o Busybox).
Davidmh

Respostas:


7

Use este comando:

awk '{ name[$1]++ }
    END { PROCINFO["sorted_in"] = "@ind_str_asc"
        for (v1 in name) for (v2 in name) if (v1 < v2) print v1, v2 }
        ' files.dat

PROCINFOpode ser uma gawkextensão. Se o seu awknão suportar, deixe de fora a PROCINFO["sorted_in"] = "@ind_str_asc"linha e canalize a saída sort(se desejar que a saída seja classificada).

(Isso não requer que a entrada seja classificada.)


8
$ join -j 2 -o 1.1,2.1 file file | awk '!seen[$1,$2]++ && !seen[$2,$1]++'
a b
a c
a d
a e
b c
b d
b e
c d
c e
d e

Isso pressupõe que nenhuma linha no arquivo de entrada contenha espaço em branco. Ele também assume que o arquivo está classificado .

O joincomando cria o produto cruzado completo das linhas no arquivo. Isso é feito juntando o arquivo a si próprio em um campo inexistente. O não padrão -j 2pode ser substituído por -1 2 -2 2(mas não por, a -j2menos que você use o GNU join).

O awkcomando lê o resultado disso e apenas gera resultados que são pares que ainda não foram vistos.


O que você quer dizer com "o arquivo está classificado"? Ordenado por quais critérios?
Enno

@ Enno Classificou a maneira que sort -biria classificá-lo. joinrequer arquivos de entrada classificados.
Kusalananda

8

Uma pythonsolução O arquivo de entrada é alimentado a itertools.combinationspartir da biblioteca padrão, que gera tuplas de 2 tamanhos que são formatadas e impressas na saída padrão.

python3 -c 'from itertools import combinations
with open("file") as f:
    lines = (line.rstrip() for line in f)
    lines = ("{} {}".format(x, y) for x, y in combinations(lines, 2))
    print(*lines, sep="\n")
'

6

Se você rubyinstalou:

$ ruby -0777 -F'\n' -lane '$F.combination(2) { |c| puts c.join(" ")}' ip.txt
a b
a c
a d
a e
b c
b d
b e
c d
c e
d e
  • -0777 engolir o arquivo inteiro (deve ser bom, pois é mencionado no OP que o tamanho do arquivo é pequeno)
  • -F'\n'dividido com base na nova linha, para que cada linha seja um elemento na $Fmatriz
  • $F.combination(2)gerar 2elementos de combinações por vez
  • { |c| puts c.join(" ")} imprima conforme necessário
  • se o arquivo de entrada puder conter duplicatas, use $F.uniq.combination(2)


para 3 elementos por vez:

$ ruby -0777 -F'\n' -lane '$F.combination(3) { |c| puts c.join(" ")}' ip.txt
a b c
a b d
a b e
a c d
a c e
a d e
b c d
b c e
b d e
c d e


Com perl(não genérico)

$ perl -0777 -F'\n' -lane 'for $i (0..$#F) {
                             for $j ($i+1..$#F) { 
                               print "$F[$i] $F[$j]\n" } }' ip.txt
a b
a c
a d
a e
b c
b d
b e
c d
c e
d e


Com awk

$ awk '{ a[NR]=$0 }
       END{ for(i=1;i<=NR;i++)
              for(j=i+1;j<=NR;j++)
                print a[i], a[j] }' ip.txt 
a b
a c
a d
a e
b c
b d
b e
c d
c e
d e

5

Aqui está um em casca pura.

test $# -gt 1 || exit
a=$1
shift
for f in "$@"
do
  echo $a $f
done
exec /bin/sh $0 "$@"

Exemplo:

~ (137) $ sh test.sh $(cat file.dat)
a b
a c
a d
a e
b c
b d
b e
c d
c e
d e
~ (138) $ 

1
Tiras de substituição de comando arrastando novas linhas, para que você é melhor fora com algo como <file.dat xargs test.shquetest.sh $(cat file.dat)
Iruvar

1

Usando Perlpodemos fazê-lo como mostrado:

$ perl -lne '
     push @A, $_}{
     while ( @A ) {
        my $e = shift @A;
        print "$e $_" for @A;
     }
' input.txt
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.