Bash - associez chaque ligne de fichier


10

Cette question est fortement liée à ceci et à cette question. J'ai un fichier qui contient plusieurs lignes où chaque ligne est un chemin d'accès à un fichier. Maintenant, je veux coupler chaque ligne avec chaque ligne différente (pas elle-même). De plus, une paire A Best égale à une B Apaire pour mes besoins, donc une seule de ces combinaisons doit être produite.

Exemple

files.dat se lit comme ceci dans une notation abrégée, chaque lettre est un chemin de fichier (absolu ou relatif)

a
b
c
d
e

Ensuite, mon résultat devrait ressembler à ceci:

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

De préférence, je voudrais résoudre ce problème en bash. Contrairement aux autres questions, ma liste de fichiers est plutôt petite (environ 200 lignes), donc l'utilisation de boucles et de capacité RAM ne pose aucun problème.


Doit-il être en bash proprement dit, ou simplement quelque chose disponible via la ligne de commande bash? D'autres utilitaires sont mieux placés pour traiter le texte.
Jeff Schaller

@JeffSchaller Quelque chose accessible via la ligne de commande bash. J'étais un peu flou, désolé
Enno

Cela devient presque un Code Golf : P
Richard de Wit

3
En règle générale, tant que vous devez faire quelque chose de non trivial, utilisez votre langage de script préféré sur BASH. Il sera moins fragile (par exemple, contre des caractères spéciaux ou des espaces), et beaucoup plus facile à étendre quand vous en aurez besoin (si vous en avez besoin de trois, ou en filtrer certains). Python ou Perl doivent être installés dans presque toutes les boîtes Linux, donc ce sont de bons choix (sauf si vous travaillez sur des systèmes embarqués, comme Busybox).
Davidmh

Réponses:


7

Utilisez cette commande:

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

PROCINFOpeut être une gawkextension. Si votre awkne le prend pas en charge, laissez simplement la PROCINFO["sorted_in"] = "@ind_str_asc"ligne et canalisez la sortie sort(si vous voulez que la sortie soit triée).

(Cela ne nécessite pas le tri de l'entrée.)


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

Cela suppose qu'aucune ligne du fichier d'entrée ne contient d'espace. Il suppose également que le fichier est trié .

La joincommande crée le produit croisé complet des lignes du fichier. Pour ce faire, il joint le fichier avec lui-même sur un champ inexistant. Le non standard -j 2peut être remplacé par -1 2 -2 2(mais pas par -j2sauf si vous utilisez GNU join).

La awkcommande lit le résultat de cette opération et ne produit que des résultats qui sont des paires qui n'ont pas encore été vues.


Qu'entendez-vous par «le fichier est trié»? Triés selon quels critères?
Enno

@Enno Trié comme le sort -bferait le tri. joinnécessitent des fichiers d'entrée triés.
Kusalananda

8

Une pythonsolution. Le fichier d'entrée est alimenté à itertools.combinationspartir de la bibliothèque standard, qui génère des tuples de 2 longueurs qui sont formatés et imprimés sur la sortie 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

Si vous avez rubyinstallé:

$ 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 slurp tout le fichier (devrait être correct car il est mentionné dans OP que la taille du fichier est petite)
  • -F'\n'fractionné en fonction de la nouvelle ligne, de sorte que chaque ligne sera un élément du $Ftableau
  • $F.combination(2)générer des combinaisons d' 2éléments à la fois
  • { |c| puts c.join(" ")} imprimer au besoin
  • si le fichier d'entrée peut contenir des doublons, utilisez $F.uniq.combination(2)


pour 3 éléments à la fois:

$ 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


Avec perl(non générique)

$ 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


Avec 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

En voici un en coquille pure.

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

Exemple:

~ (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
Bandes de substitution commande sauts de lignes, de sorte que vous êtes mieux avec quelque chose comme <file.dat xargs test.shquetest.sh $(cat file.dat)
Iruvar

1

En utilisant Perlnous pouvons le faire comme indiqué:

$ perl -lne '
     push @A, $_}{
     while ( @A ) {
        my $e = shift @A;
        print "$e $_" for @A;
     }
' input.txt
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.