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.

Enno
fonte
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.)

G-Man diz que 'restabelece Monica'
fonte
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.

Kusalananda
fonte
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")
'
iruvar
fonte
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
Sundeep
fonte
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) $ 
EdC
fonte
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
Rakesh Sharma
fonte