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.