Outils Linux pour traiter les fichiers comme des ensembles et y effectuer des opérations

82

Est-ce que quelqu'un connaît un outil Linux spécialement conçu pour traiter les fichiers comme des ensembles et y effectuer des opérations d'ensemble? Comme différence, intersection, etc.?

nilton
la source

Réponses:

110

En supposant que les éléments soient des chaînes de caractères autres que NUL et newline (sachez que newline est valide dans les noms de fichier), vous pouvez représenter un ensemble sous forme de fichier texte avec un élément par ligne et utiliser certains des utilitaires Unix standard.

Définir un abonnement

$ grep -Fxc 'element' set   # outputs 1 if element is in set
                            # outputs >1 if set is a multi-set
                            # outputs 0 if element is not in set

$ grep -Fxq 'element' set   # returns 0 (true)  if element is in set
                            # returns 1 (false) if element is not in set

$ awk '$0 == "element" { s=1; exit }; END { exit !s }' set
# returns 0 if element is in set, 1 otherwise.

$ awk -v e='element' '$0 == e { s=1; exit } END { exit !s }'

Définir l'intersection

$ comm -12 <(sort set1) <(sort set2)  # outputs intersect of set1 and set2

$ grep -xF -f set1 set2

$ sort set1 set2 | uniq -d

$ join -t <(sort A) <(sort B)

$ awk '!done { a[$0]; next }; $0 in a' set1 done=1 set2

Définir l'égalité

$ cmp -s <(sort set1) <(sort set2) # returns 0 if set1 is equal to set2
                                   # returns 1 if set1 != set2

$ cmp -s <(sort -u set1) <(sort -u set2)
# collapses multi-sets into sets and does the same as previous

$ awk '{ if (!($0 in a)) c++; a[$0] }; END{ exit !(c==NR/2) }' set1 set2
# returns 0 if set1 == set2
# returns 1 if set1 != set2

$ awk '{ a[$0] }; END{ exit !(length(a)==NR/2) }' set1 set2
# same as previous, requires >= gnu awk 3.1.5

Définir la cardinalité

$ wc -l < set     # outputs number of elements in set

$ awk 'END { print NR }' set

$ sed '$=' set

Test de sous-ensemble

$ comm -23 <(sort -u subset) <(sort -u set) | grep -q '^'
# returns true iff subset is not a subset of set (has elements not in set)

$ awk '!done { a[$0]; next }; { if !($0 in a) exit 1 }' set done=1 subset
# returns 0 if subset is a subset of set
# returns 1 if subset is not a subset of set

Set Union

$ cat set1 set2     # outputs union of set1 and set2
                    # assumes they are disjoint

$ awk 1 set1 set2   # ditto

$ cat set1 set2 ... setn   # union over n sets

$ sort -u set1 set2  # same, but doesn't assume they are disjoint

$ sort set1 set2 | uniq

$ awk '!a[$0]++' set1 set2       # ditto without sorting

Set Complement

$ comm -23 <(sort set1) <(sort set2)
# outputs elements in set1 that are not in set2

$ grep -vxF -f set2 set1           # ditto

$ sort set2 set2 set1 | uniq -u    # ditto

$ awk '!done { a[$0]; next }; !($0 in a)' set2 done=1 set1

Définir la différence symétrique

$ comm -3 <(sort set1) <(sort set2) | tr -d '\t'  # assumes not tab in sets
# outputs elements that are in set1 or in set2 but not both

$ sort set1 set2 | uniq -u

$ cat <(grep -vxF -f set1 set2) <(grep -vxF -f set2 set1)

$ grep -vxF -f set1 set2; grep -vxF -f set2 set1

$ awk '!done { a[$0]; next }; $0 in a { delete a[$0]; next }; 1;
       END { for (b in a) print b }' set1 done=1 set2

Ensemble de puissance

Tous les sous-ensembles possibles d’un ensemble d’espaces affichés séparés, un par ligne:

$ p() { [ "$#" -eq 0 ] && echo || (shift; p "$@") |
        while read r; do printf '%s %s\n%s\n' "$1" "$r" "$r"; done; }
$ p $(cat set)

(suppose que les éléments ne contiennent pas SPC, TAB (en supposant la valeur par défaut $IFS), barre oblique inverse, caractères génériques).

Définir un produit cartésien

$ while IFS= read -r a; do while IFS= read -r b; do echo "$a, $b"; done < set1; done < set2

$ awk '!done { a[$0]; next }; { for (i in a) print i, $0 }' set1 done=1 set2

Test d'ensemble disjoint

$ comm -12 <(sort set1) <(sort set2)  # does not output anything if disjoint

$ awk '++seen[$0] == 2 { exit 1 }' set1 set2 # returns 0 if disjoint
                                             # returns 1 if not

Test d'ensemble vide

$ wc -l < set            # outputs 0  if the set is empty
                         # outputs >0 if the set is not empty

$ grep -q '^' set        # returns true (0 exit status) unless set is empty

$ awk '{ exit 1 }' set   # returns true (0 exit status) if set is empty

Le minimum

$ sort set | head -n 1   # outputs the minimum (lexically) element in the set

$ awk 'NR == 1 { min = $0 }; $0 < min { min = $0 }; END { print min }'
# ditto, but does numeric comparison when elements are numerical

Maximum

$ sort test | tail -n 1    # outputs the maximum element in the set

$ sort -r test | head -n 1

$ awk '$0 > max { max = $0 }; END { print max }'
# ditto, but does numeric comparison when elements are numerical

Tous disponibles à http://www.catonmat.net/blog/set-operations-in-unix-shell-simplified/

llhuii
la source
1
Je pense que la version Python est beaucoup plus simple et intuitive. ;-)
Keith
Je pense que c'est la réponse la plus complète. Malheureusement, les commandes à exécuter ou les arguments (comm -12, -23, -13) dans chaque cas n’est pas toujours intuitif comme "intersection" ou "différence". Peut-être que je créerai une enveloppe autour d'eux, puisque j'utilise toujours ces choses.
nilton
J'ai exécuté [pol @ localhost inst] $ grep -xc et INSTALL-BINARY 0 [pol @ localhost inst] $ mais je ne comprends pas ce que cela signifie. Le mot "et" doit apparaître plusieurs fois dans le fichier. Qu'est-ce que je fais mal?
Vérace
1
Intersection d'ensembles: sort set1 set2 | uniq -dne fonctionne pas pour les ensembles multiples. Pensez à utiliser sort <(sort -u set1) <(sort -u set2) | uniq -d.
neo
11

Sorte de. Vous devez gérer vous-même le tri, mais vous commpouvez l'utiliser pour traiter chaque ligne comme un membre du groupe: -12intersection, -13différence. (Et -23vous donne une différence inversée, c'est-à-dire set2 - set1au lieu de set1 - set2.) Union est sort -udans cette configuration.

geekosaur
la source
1
En effet, les communications semblent faire la plupart des choses. Bien que les arguments sont très peu intuitifs. Merci!
nilton
7

Je ne connais pas d'outil spécifique, mais vous pouvez utiliser Python, sa classe de jeu et ses opérateurs, pour écrire un petit script.

Pour exampe:

Python> s1 = set(os.listdir("/bin"))
Python> s2 = set(os.listdir("/usr/bin"))
Python> s1 & s2

set(['awk',
     'basename',
     'chroot', ...
Keith
la source
Oui, bonne réponse. Pourquoi utiliser awk si Python est disponible?
Guettli
Vous avez oublié:Python> import os
James Bowery
7

Le petit outil de console «setop» est maintenant disponible dans Debian Stretch et Ubuntu depuis le 16.10. Vous pouvez l'obtenir via sudo apt install setop

Voici quelques exemples. Les ensembles à utiliser sont donnés sous forme de fichiers d’entrée différents: setop input # is equal to "sort input --unique" setop file1 file2 --union # option --union is default and can be omitted setop file1 file2 file3 --intersection # more than two inputs are allowed setop file1 - --symmetric-difference # ndash stands for standard input setop file1 -d file2 # all elements contained in 1 but not 2

Les requêtes booléennes ne renvoient que EXIT_SUCCESSdans le cas de true et, EXIT_FAILUREsinon, dans un message. De cette façon, setop peut être utilisé dans le shell. setop inputfile --contains "value" # is element value contained in input? setop A.txt B.txt --equal C.txt # union of A and B equal to C? setop bigfile --subset smallfile # analogous --superset setop -i file1 file2 --is-empty # intersection of 1 and 2 empty (disjoint)?

Il est également possible de décrire précisément comment les flux d'entrée doivent être analysés, en fait par des expressions régulières:

  • setop input.txt --input-separator "[[:space:]-]"signifie qu'un espace ( \v \t \n \r \fou espace) ou un signe moins est interprété comme un séparateur entre les éléments (par défaut, nouvelle ligne, autrement dit, chaque ligne du fichier d'entrée correspond à un élément)
  • setop input.txt --input-element "[A-Za-z]+" signifie que les éléments ne sont que des mots composés de caractères latins, tous les autres caractères sont considérés comme des séparateurs entre les éléments

De plus, vous pouvez

  • --count tous les éléments du jeu de sortie,
  • --trim tous les éléments d'entrée (c.-à-d. effacez tous les caractères précédents et suivants indésirables comme l'espace, les virgules, etc.),
  • considérer les éléments vides comme valables via --include-empty,
  • --ignore-case,
  • définir les --output-separatorentre les éléments du flux de sortie (la valeur par défaut est \n),
  • etc.

Voir man setopou github.com/phisigma/setop pour plus d'informations.

Franc
la source
3

Si vous voyez un fichier comme un ensemble de lignes, et les fichiers sont triés, il y a comm.

Si vous voyez un fichier comme un (plusieurs) ensemble de lignes et que les lignes ne sont pas triées, vous greppouvez créer une différence et une intersection (cela permet d'obtenir une différence et une intersection définies, mais ne respecte pas le décompte des multisets). L'union est juste cat.

grep -xF -f small large >intersection
grep -vxF -f small large >difference
cat small large >union
Gilles
la source
2

J'ai créé un utilitaire Python capable de créer des unions, des intersections, des différences et des produits de plusieurs fichiers. Il s’appelle SetOp, vous pouvez le trouver sur PyPI ( ici ). La syntaxe ressemble à ceci:

$ setop -i file1 file2 file3  # intersection
$ setop -d file1 file2 file3  # difference
Tigr
la source
1

J'ai écrit un petit outil pour le faire qui m'a été très utile à divers endroits. L'interface utilisateur n'est pas polie et je ne suis pas sûr des caractéristiques de performance pour les très gros fichiers (puisqu'il lit toute la liste en mémoire), mais "ça marche pour moi". Le programme est disponible sur https://github.com/nibrahim/lines . C'est en Python. Vous pouvez l'obtenir en utilisant pip install lines.

Il supporte actuellement l'union, l'intersection, la différence et la différence symétrique de deux fichiers. Chaque ligne du fichier d'entrée est traitée comme un élément d'un ensemble.

Il a également deux opérations supplémentaires. Une des lignes vides dans un fichier et la seconde (qui m’a été très utile) consiste à parcourir le fichier et à le diviser en ensembles de chaînes similaires. J'avais besoin de cela pour rechercher des fichiers dans une liste qui ne correspondait pas au modèle général.

Je serais heureux de recevoir vos commentaires.

Noufal Ibrahim
la source
0

Le système de fichiers considère les noms de fichiers (noms de fichiers entiers, y compris les chemins) comme uniques.

Des opérations?

Vous pouvez copier les fichiers de / et b / dans le répertoire vide c / pour obtenir un nouveau jeu d’unions.

Avec des tests de fichiers tels que -e nameet des boucles ou find, vous pouvez rechercher des fichiers existant dans plusieurs répertoires afin d’obtenir l’intersection ou la différence.

Utilisateur inconnu
la source
1
Je voulais dire traiter le contenu des fichiers en tant qu'éléments d'un ensemble (disons, un élément par ligne) et les fichiers eux-mêmes en tant qu'ensembles.
nilton
0

Meilleure réponse ici: Setdown (un outil dédié)

J'ai écrit un programme appelé setdown qui effectue les opérations Set à partir de la CLI.

Il peut effectuer des opérations sur les ensembles en écrivant une définition similaire à celle que vous écririez dans un Makefile:

someUnion: "file-1.txt" \/ "file-2.txt"
someIntersection: "file-1.txt" /\ "file-2.txt"
someDifference: someUnion - someIntersection

C'est plutôt cool et vous devriez y jeter un coup d'œil. Personnellement, je ne recommande pas l’utilisation de commandes ad-hoc qui n’ont pas été créées pour que le travail puisse exécuter des opérations définies. . Non seulement cela, mais setdown vous permet d'écrire des opérations de set qui dépendent d'autres opérations de set!

En tout cas, je pense que c'est plutôt cool et que tu devrais le vérifier totalement.

Robert Massaioli
la source
0

Exemple de modèle pour plusieurs fichiers (intersection dans ce cas):

eval `perl -le 'print "cat ",join(" | grep -xF -f- ", @ARGV)' t*`

S'étend à:

cat t1 | grep -xF -f- t2 | grep -xF -f- t3

Fichiers de test:

seq 0 20 | tee t1; seq 0 2 20 | tee t2; seq 0 3 20 | tee t3

Sortie:

0
6
12
18
bsb
la source
0

Avec les zshtableaux (les zshtableaux peuvent contenir n’importe quelle séquence d’octets, même 0).

(notez également que vous pouvez faire en sorte que typeset -U arrayses éléments soient uniques).

définir l'adhésion

if ((${array[(Ie)$element]})); then
  echo '$element is in $array'
fi

(en utilisant le Idrapeau indice de tableau, pour obtenir l'index de la dernière occurrence de $elementdans le tableau (ou 0 si non trouvé). Remove e(pour exact) pour $elementêtre pris comme modèle)

if ((n = ${(M)#array:#$element})); then
  echo "\$element is found $n times in \$array'
fi

${array:#pattern}étant une variation sur ksh ${var#pattern}qui supprime les éléments qui correspondent au motif plutôt que de simplement supprimer la partie principale qui correspond au motif. Le paramètre(M) (pour la correspondance ) inverse le sens et supprime tous les éléments sauf les éléments correspondants (utilisez-le $~elementpour qu'il soit pris comme motif).

définir l'intersection

common=("${(@)set1:*set2}")

${set1:*set2}effectue l'intersection du tableau, mais la "${(@)...}"syntaxe est nécessaire pour conserver les éléments vides.

établir l'égalité

[[ ${(j: :)${(q)array1}} = ${(j: :)${(q)array2}} ]]

Teste si les tableaux sont identiques (et dans le même ordre). Le qparamètre de développement de paramètre cite les éléments (pour éviter les problèmes liés à des choses comme a=(1 "2 3")vs b=("1 2" 3)), et les (j: :)joint à l’espace avant de procéder à une comparaison de chaîne.

Pour vérifier qu'ils ont les mêmes éléments, quel que soit leur ordre, utilisez le odrapeau pour les commander. Voir aussi le udrapeau (unique) pour supprimer les doublons.

[[ ${(j: :)${(qo)array1}} = ${(j: :)${(qo)array2}} ]]

définir la cardinalité

n=$#array

test de sous-ensemble

if ((${#array1:*array2} == ${#array2})); then
  echo '$array2 is included in $array1'
fi

syndicat

union=("$array1[@]" "$array2[@]")

(voir typeset -Uci - dessus ou l' uindicateur d'expansion de paramètre pour prendre en compte les doublons). Encore une fois, si la chaîne vide ne fait pas partie des valeurs possibles, vous pouvez simplifier pour:

union=($array1 $array2)

complément

complement=("${(@)array1:|array2}")

pour les éléments de $array1ce ne sont pas dans $array2.

minimum / maximum (comparaison lexicale)

min=${${(o)array}[1]} max=${${(o)array}[-1]}

minimum / maximum (comparaison d'entiers décimaux)

min=${${(no)array}[1]} max=${${(no)array}[-1]}
Stéphane Chazelas
la source