Bash - accoppia ogni riga di file


10

Questa domanda è fortemente correlata a questa e questa domanda. Ho un file che contiene diverse righe in cui ogni riga è un percorso di un file. Ora voglio accoppiare ogni riga con ogni riga diversa (non se stessa). Anche una coppia A Bè uguale a una B Acoppia per i miei scopi, quindi solo una di queste combinazioni dovrebbe essere prodotta.

Esempio

files.dat legge in questo modo in una notazione abbreviata, ogni lettera è un percorso di file (assoluto o relativo)

a
b
c
d
e

Quindi il mio risultato dovrebbe assomigliare a questo:

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

Preferibilmente vorrei risolverlo in bash. A differenza delle altre domande, il mio elenco di file è piuttosto piccolo (circa 200 righe), quindi l'utilizzo di loop e capacità della RAM non pone problemi.


Deve essere nella bash corretta o solo qualcosa disponibile dalla riga di comando bash? Altre utilità sono posizionate meglio per elaborare il testo.
Jeff Schaller

@JeffSchaller Qualcosa di accessibile dalla riga di comando bash. Ero un po 'poco chiaro, scusa
Enno

Questo sta diventando quasi un Code Golf : P
Richard de Wit,

3
Come regola generale, fintanto che devi fare qualcosa di non banale, usa il tuo linguaggio di scripting preferito su BASH. Sarà meno fragile (ad esempio, contro caratteri o spazi speciali) e molto più facile da espandere ogni volta che ne avrai bisogno (se ne hai bisogno tre, o filtrane alcuni). Python o Perl dovrebbero essere installati in quasi tutti i box Linux, quindi sono buone scelte (a meno che tu non stia lavorando su sistemi embedded, come Busybox).
Davidmh,

Risposte:


7

Usa questo 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

PROCINFOpotrebbe essere gawkun'estensione. Se il tuo awknon lo supporta, lascia fuori la PROCINFO["sorted_in"] = "@ind_str_asc"linea e reindirizza l'output sort(se vuoi che l'output sia ordinato).

(Questo non richiede che l'input sia ordinato.)


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

Ciò presuppone che nessuna riga nel file di input contenga spazi bianchi. Presuppone inoltre che il file sia ordinato .

Il joincomando crea l'intero prodotto incrociato delle righe nel file. Lo fa unendo il file con se stesso su un campo inesistente. Il non standard -j 2può essere sostituito da -1 2 -2 2(ma non da -j2se non si utilizza GNU join).

Il awkcomando legge il risultato di questo e genera solo risultati che sono coppie che non sono state ancora viste.


Cosa intendi con "il file è ordinato"? Ordinati secondo quali criteri?
Enno

@Enno Ordinato il modo in cui l' sort -bordinerebbe. joinrichiede file di input ordinati.
Kusalananda

8

Una pythonsoluzione Il file di input viene alimentato itertools.combinationsdalla libreria standard, che genera tuple a 2 lunghezze che vengono formattate e stampate sull'output standard.

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 hai rubyinstallato:

$ 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 bere tutto il file (dovrebbe essere a posto come indicato in OP che le dimensioni del file sono ridotte)
  • -F'\n'diviso in base a newline, quindi ogni riga sarà un elemento nella $Fmatrice
  • $F.combination(2)genera combinazioni di 2elementi alla volta
  • { |c| puts c.join(" ")} stampa come richiesto
  • se il file di input può contenere duplicati, utilizzare $F.uniq.combination(2)


per 3 elementi alla volta:

$ 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


Con perl(non generico)

$ 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


Con 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

Eccone uno in puro guscio.

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

Esempio:

~ (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
Strisce di sostituzione di comando finale a capo, quindi si sta meglio con qualcosa di simile <file.dat xargs test.shchetest.sh $(cat file.dat)
Iruvar

1

Usando Perlpossiamo farlo come mostrato:

$ perl -lne '
     push @A, $_}{
     while ( @A ) {
        my $e = shift @A;
        print "$e $_" for @A;
     }
' input.txt
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.