Bash: empareja cada línea de archivo


10

Esta pregunta está fuertemente relacionada con esta y esta pregunta. Tengo un archivo que contiene varias líneas donde cada línea es una ruta a un archivo. Ahora quiero emparejar cada línea con cada línea diferente (no en sí misma). También un par A Bes igual a un B Apar para mis propósitos, por lo que solo se debe producir una de estas combinaciones.

Ejemplo

files.dat se lee así en una notación abreviada, cada letra es una ruta de archivo (absoluta o relativa)

a
b
c
d
e

Entonces mi resultado debería verse más o menos así:

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

Preferiblemente me gustaría resolver esto en bash. A diferencia de las otras preguntas, mi lista de archivos es bastante pequeña (aproximadamente 200 líneas), por lo que usar bucles y capacidad de RAM no plantea problemas.


¿Tiene que estar en bash propiamente dicho, o simplemente algo disponible a través de la línea de comando bash? Otras utilidades están mejor posicionadas para procesar texto.
Jeff Schaller

@JeffSchaller Algo accesible a través de la línea de comandos bash. Estaba un poco confuso, lo siento
Enno

Esto casi se está convirtiendo en un Código Golf : P
Richard de Wit

3
Como regla general, siempre que necesite hacer algo no trivial, use su lenguaje de script favorito sobre BASH. Será menos frágil (por ejemplo, contra caracteres o espacios especiales), y mucho más fácil de expandir cuando lo necesite (si necesita tres, o filtre algunos de ellos). Python o Perl deben instalarse en casi cualquier caja de Linux, por lo que son buenas opciones (a menos que esté trabajando en sistemas integrados, como Busybox).
Davidmh

Respuestas:


7

Usa 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

PROCINFOPuede ser una gawkextensión. Si awkno lo admite, simplemente omita la PROCINFO["sorted_in"] = "@ind_str_asc"línea y canalice la salida ensort (si desea ordenar la salida).

(Esto no requiere que se ordene la entrada).


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

Esto supone que ninguna línea en el archivo de entrada contiene espacios en blanco. También supone que el archivo está ordenado .

El joincomando crea el producto cruzado completo de las líneas en el archivo. Lo hace uniendo el archivo consigo mismo en un campo no existente. El no estándar -j 2puede ser reemplazado por -1 2 -2 2(pero no por a -j2menos que use GNUjoin ).

El awkcomando lee el resultado de esto y solo genera resultados que son pares que aún no se han visto.


¿Qué quiere decir con "el archivo está ordenado"? ¿Ordenado por qué criterio?
Enno

@Enno Sorted la forma en que lo sort -bordenaría. joinrequieren archivos de entrada ordenados.
Kusalananda

8

Una pythonsolución El archivo de entrada se alimenta itertools.combinationsdesde la biblioteca estándar, que genera tuplas de 2 longitudes formateadas e impresas en la salida estándar.

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

Si ha rubyinstalado:

$ 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 sorber todo el archivo (debería estar bien, ya que se menciona en OP que el tamaño del archivo es pequeño)
  • -F'\n'dividido según la nueva línea, por lo que cada línea será un elemento en la $Fmatriz
  • $F.combination(2)generar 2elementos de combinación a la vez
  • { |c| puts c.join(" ")} imprimir según sea necesario
  • si el archivo de entrada puede contener duplicados, use $F.uniq.combination(2)


para 3 elementos a la 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


Con perl(no 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


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

Aquí hay uno en pura concha.

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

Ejemplo:

~ (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 sustitución de comandos detrás de los saltos de línea, por lo que es mejor con algo así como <file.dat xargs test.shquetest.sh $(cat file.dat)
Iruvar

1

Usando Perlpodemos hacerlo como se muestra:

$ perl -lne '
     push @A, $_}{
     while ( @A ) {
        my $e = shift @A;
        print "$e $_" for @A;
     }
' input.txt
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.