Intersection de deux tableaux dans BASH

12

J'ai deux tableaux comme celui-ci:

A=(vol-175a3b54 vol-382c477b vol-8c027acf vol-93d6fed0 vol-71600106 vol-79f7970e vol-e3d6a894 vol-d9d6a8ae vol-8dbbc2fa vol-98c2bbef vol-ae7ed9e3 vol-5540e618 vol-9e3bbed3 vol-993bbed4 vol-a83bbee5 vol-ff52deb2)
B=(vol-175a3b54 vol-e38d0c94 vol-2a19386a vol-b846c5cf vol-98c2bbef vol-7320102b vol-8f6226cc vol-27991850 vol-71600106 vol-615e1222)

Les tableaux ne sont pas triés et peuvent même contenir des éléments dupliqués.

  1. Je voudrais faire l'intersection de ces deux tableaux et stocker les éléments dans un autre tableau. Comment ferais-je ça?

  2. De plus, comment puis-je obtenir la liste des éléments qui apparaissent en B et qui ne sont pas disponibles en A?

Bogdan
la source
2
Utilisez un vrai langage de programmation, pas un shell pour ce genre de tâche.
Stéphane Chazelas
1
Devez-vous conserver l'ordre des éléments? S'il y a des éléments dupliqués (par exemple A et B contiennent foodeux fois), avez-vous besoin qu'ils soient dupliqués dans le résultat?
Gilles 'SO- arrête d'être méchant'

Réponses:

13

comm(1)est un outil qui compare deux listes et peut vous donner l'intersection ou la différence entre deux listes. Les listes doivent être triées, mais c'est facile à réaliser.

Pour obtenir vos tableaux dans une liste triée adaptée à comm:

$ printf '%s\n' "${A[@]}" | LC_ALL=C sort

Cela transformera le tableau A en une liste triée. Faites de même pour B.

Pour utiliser commpour renvoyer l'intersection:

$ comm -1 -2 file1 file2

-1 -2 dit de supprimer les entrées uniques au fichier1 (A) et uniques au fichier2 (B) - l'intersection des deux.

Pour qu'il renvoie ce qui est dans le fichier2 (B) mais pas dans le fichier1 (A):

$ comm -1 -3 file1 file2

-1 -3 dit de supprimer les entrées uniques à file1 et communes aux deux - ne laissant que celles uniques à file2.

Pour alimenter deux pipelines comm, utilisez la fonction "Substitution de processus" de bash:

$ comm -1 -2 <(pipeline1) <(pipeline2)

Pour capturer cela dans un tableau:

$ C=($(command))

Mettre tous ensemble:

# 1. Intersection
$ C=($(comm -12 <(printf '%s\n' "${A[@]}" | LC_ALL=C sort) <(printf '%s\n' "${B[@]}" | LC_ALL=C sort)))

# 2. B - A
$ D=($(comm -13 <(printf '%s\n' "${A[@]}" | LC_ALL=C sort) <(printf '%s\n' "${B[@]}" | LC_ALL=C sort)))
camh
la source
Cela ne fonctionnera que si vos valeurs ne contiennent pas \n.
Chris Down
@ChrisDown: C'est vrai. J'essaie toujours d'écrire des scripts shell correctement cités et de gérer tous les caractères, mais j'ai abandonné \ n. Je ne l'ai jamais vu dans un nom de fichier, et un grand nombre d'outils Unix fonctionnent avec des enregistrements délimités \ n que vous perdez beaucoup si vous essayez de gérer \ n comme un caractère valide.
camh
1
Je l'ai vu dans les noms de fichiers lors de l'utilisation de gestionnaires de fichiers GUI qui ne nettoient pas correctement les noms de fichiers d'entrée qui sont copiés ailleurs (également, personne n'a dit quoi que ce soit à propos des noms de fichiers).
Chris Down
Pour protéger, \nessayez ceci:arr1=( one two three "four five\nsix\nseven" ); arr2=( ${arr1[@]:1} "four five\\nsix" ); n1=${#arr1[@]}; n2=${#arr2[@]}; arr=( ${arr1[@]/ /'-_-'} ${arr2[@]/ /'-_-'} ); arr=( $( echo "${arr[@]}"|tr '\t' '-t-'|tr '\n' '-n-'|tr '\r' '-r-' ) ); arr1=( ${arr[@]:0:${n1}} ); arr2=( ${arr[@]:${n1}:${n2}} ); unset arr; printf "%0.s-" {1..10}; printf '\n'; printf '{'; printf " \"%s\" " "${arr1[@]}"; printf '}\n'; printf "%0.s-" {1..10}; printf '\n'; printf '{'; printf " \"%s\" " "${arr2[@]}"; printf '}\n'; printf "%0.s-" {1..10}; printf '\n\n'; unset arr1; unset arr2
Jason R. Mick
Il ne faut pas se coucher LC_ALL=C. Au lieu de cela, réglez LC_COLLATE=Cle même gain de performances sans autres effets secondaires. Afin d'obtenir des résultats corrects, vous devrez également définir le même classement que commcelui utilisé pour sort, par exemple:unset LC_ALL; LC_COLLATE=C ; comm -12 <(printf '%s\n' "${A[@]}" | sort) <(printf '%s\n' "${B[@]}" | sort)
Sorpigal
4

Vous pouvez obtenir tous les éléments qui se trouvent à la fois dans A et B en parcourant les deux tableaux et en comparant:

A=(vol-175a3b54 vol-382c477b vol-8c027acf vol-93d6fed0 vol-71600106 vol-79f7970e vol-e3d6a894 vol-d9d6a8ae vol-8dbbc2fa vol-98c2bbef vol-ae7ed9e3 vol-5540e618 vol-9e3bbed3 vol-993bbed4 vol-a83bbee5 vol-ff52deb2)
B=(vol-175a3b54 vol-e38d0c94 vol-2a19386a vol-b846c5cf vol-98c2bbef vol-7320102b vol-8f6226cc vol-27991850 vol-71600106 vol-615e1222)

intersections=()

for item1 in "${A[@]}"; do
    for item2 in "${B[@]}"; do
        if [[ $item1 == "$item2" ]]; then
            intersections+=( "$item1" )
            break
        fi
    done
done

printf '%s\n' "${intersections[@]}"

Vous pouvez obtenir tous les éléments en B mais pas en A de la même manière:

A=(vol-175a3b54 vol-382c477b vol-8c027acf vol-93d6fed0 vol-71600106 vol-79f7970e vol-e3d6a894 vol-d9d6a8ae vol-8dbbc2fa vol-98c2bbef vol-ae7ed9e3 vol-5540e618 vol-9e3bbed3 vol-993bbed4 vol-a83bbee5 vol-ff52deb2)
B=(vol-175a3b54 vol-e38d0c94 vol-2a19386a vol-b846c5cf vol-98c2bbef vol-7320102b vol-8f6226cc vol-27991850 vol-71600106 vol-615e1222)

not_in_a=()

for item1 in "${B[@]}"; do
    for item2 in "${A[@]}"; do
        [[ $item1 == "$item2" ]] && continue 2
    done

    # If we reached here, nothing matched.
    not_in_a+=( "$item1" )
done

printf '%s\n' "${not_in_a[@]}"
Chris Down
la source
Exercice: si vous échangez Aet B, est-ce intersectionstoujours la même chose pour réorganiser?
Gilles 'SO- arrête d'être méchant'
@Gilles Si les tableaux peuvent contenir des éléments en double, non.
Chris Down
3

Il existe une approche assez élégante et efficace pour ce faire, en utilisant uniq- mais, nous devrons éliminer les doublons de chaque tableau, ne laissant que des éléments uniques. Si vous souhaitez enregistrer les doublons, il n'y a qu'une seule façon "de parcourir les deux tableaux et de comparer".

Considérez que nous avons deux tableaux:

A=(vol-175a3b54 vol-382c477b vol-8c027acf vol-93d6fed0 vol-71600106 vol-79f7970e vol-e3d6a894 vol-d9d6a8ae vol-8dbbc2fa vol-98c2bbef vol-ae7ed9e3 vol-5540e618 vol-9e3bbed3 vol-993bbed4 vol-a83bbee5 vol-ff52deb2)
B=(vol-175a3b54 vol-e38d0c94 vol-2a19386a vol-b846c5cf vol-98c2bbef vol-7320102b vol-8f6226cc vol-27991850 vol-71600106 vol-615e1222)

Tout d'abord, transformons ces tableaux en ensembles. Nous le ferons parce qu'il ya intersection opération mathématique qui est connu comme intersection des ensembles, et ensemble est une collection de différents objets, distincts ou uniques . Pour être honnête, je ne sais pas ce qu'est "l'intersection" si nous parlons de listes ou de séquences. Bien que nous puissions choisir une sous-séquence de la séquence, mais cette opération (sélection) a une signification légèrement différente.

Alors, transformons-nous!

$ A=(echo ${A[@]} | sed 's/ /\n/g' | sort | uniq)
$ B=(echo ${B[@]} | sed 's/ /\n/g' | sort | uniq)
  1. Intersection:

    $ echo ${A[@]} ${B[@]} | sed 's/ /\n/g' | sort | uniq -d

    Si vous souhaitez stocker les éléments dans un autre tableau:

    $ intersection_set=$(echo ${A[@]} ${B[@]} | sed 's/ /\n/g' | sort | uniq -d)
    
    $ echo $intersection_set
    vol-175a3b54 vol-71600106 vol-98c2bbef

    uniq -dsignifie ne montrer que les doublons (je pense, uniqc'est assez rapide à cause de sa réalisation: je suppose que c'est fait en XORfonctionnement).

  2. Obtenez la liste des éléments qui apparaissent Bet qui ne sont pas disponibles dans A, c.- à -d.B\A

    $ echo ${A[@]} ${B[@]} | sed 's/ /\n/g' | sort | uniq -d | xargs echo ${B[@]} | sed 's/ /\n/g' | sort | uniq -u

    Ou, avec sauvegarde dans une variable:

    $ subtraction_set=$(echo ${A[@]} ${B[@]} | sed 's/ /\n/g' | sort | uniq -d | xargs echo ${B[@]} | sed 's/ /\n/g' | sort | uniq -u)
    
    $ echo $subtraction_set
    vol-27991850 vol-2a19386a vol-615e1222 vol-7320102b vol-8f6226cc vol-b846c5cf vol-e38d0c94

    Ainsi, au début, nous avons obtenu l'intersection de Aet B(qui est simplement l'ensemble des doublons entre eux), disons que c'est le cas A/\B, puis nous avons utilisé l'opération d'inversion de l'intersection de Bet A/\B(qui n'est tout simplement que des éléments uniques), nous obtenons donc B\A = ! (B /\ (A/\B)).

PS a uniqété écrit par Richard M. Stallman et David MacKenzie.

kenichi
la source
1

Ignorant l'efficacité, voici une approche:

declare -a intersect
declare -a b_only
for bvol in "${B[@]}"
do
    in_both=""
    for avol in "${A[@]}"
    do
        [ "$bvol" = "$avol" ] && in_both=Yes
    done
    if [ "$in_both" ]
    then
        intersect+=("$bvol")
    else
        b_only+=("$bvol")
    fi
done
echo "intersection=${intersect[*]}"
echo "In B only=${b_only[@]}"
John1024
la source
0

Ma pure façon bash

Comme ces variables contiennent uniquement vol-XXXXXXest un nombre hexadécimal, il existe un moyen rapide d'utiliser les tableaux bash

unset A B a b c i                    # Only usefull for re-testing...

A=(vol-175a3b54 vol-382c477b vol-8c027acf vol-93d6fed0 vol-71600106 vol-79f7970e
   vol-e3d6a894 vol-d9d6a8ae vol-8dbbc2fa vol-98c2bbef vol-ae7ed9e3 vol-5540e618
   vol-9e3bbed3 vol-993bbed4 vol-a83bbee5 vol-ff52deb2)
B=(vol-175a3b54 vol-e38d0c94 vol-2a19386a vol-b846c5cf vol-98c2bbef vol-7320102b
   vol-8f6226cc vol-27991850 vol-71600106 vol-615e1222)

for i in ${A[@]#vol-};do
    [ "${a[$((16#$i))]}" ] && echo Duplicate vol-$i in A
    ((a[$((16#$i))]++))
    ((c[$((16#$i))]++))
  done
for i in ${B[@]#vol-};do
    [ "${b[$((16#$i))]}" ] && echo Duplicate vol-$i in B
    ((b[$((16#$i))]++))
    [ "${c[$((16#$i))]}" ] && echo Present in A and B: vol-$i
    ((c[$((16#$i))]++))
  done

Cela doit produire:

Present in A and B vol-175a3b54
Present in A and B vol-98c2bbef
Present in A and B vol-71600106

À cet état, votre environnement bash contient:

set | grep ^c=
c=([391789396]="2" [664344656]="1" [706295914]="1" [942425979]="1" [1430316568]="1"
[1633554978]="1" [1902117126]="2" [1931481131]="1" [2046269198]="1" [2348972751]="1"
[2377892602]="1" [2405574348]="1" [2480340688]="1" [2562898927]="2" [2570829524]="1"
[2654715603]="1" [2822487781]="1" [2927548899]="1" [3091645903]="1" [3654723758]="1"
[3817671828]="1" [3822495892]="1" [4283621042]="1")

Vous pourriez donc:

for i in ${!b[@]};do
    [ ${c[$i]} -eq 1 ] &&
        printf "Present only in B: vol-%8x\n" $i
  done

Cela rendra:

Present only in B: vol-27991850
Present only in B: vol-2a19386a
Present only in B: vol-615e1222
Present only in B: vol-7320102b
Present only in B: vol-8f6226cc
Present only in B: vol-b846c5cf
Present only in B: vol-e38d0c94

Mais c'est trié numériquement! Si vous souhaitez une commande originale, vous pouvez:

for i in ${B[@]#vol-};do
    [ ${c[((16#$i))]} -eq 1 ] && printf "Present in B only: vol-%s\n" $i
  done

Donc, vous affichez les vols dans le même ordre que celui soumis:

Present in B only: vol-e38d0c94
Present in B only: vol-2a19386a
Present in B only: vol-b846c5cf
Present in B only: vol-7320102b
Present in B only: vol-8f6226cc
Present in B only: vol-27991850
Present in B only: vol-615e1222

ou

for i in ${!a[@]};do
    [ ${c[$i]} -eq 1 ] && printf "Present only in A: vol-%8x\n" $i
  done

pour afficher uniquement en A :

Present only in A: vol-382c477b
Present only in A: vol-5540e618
Present only in A: vol-79f7970e
Present only in A: vol-8c027acf
Present only in A: vol-8dbbc2fa
Present only in A: vol-93d6fed0
Present only in A: vol-993bbed4
Present only in A: vol-9e3bbed3
Present only in A: vol-a83bbee5
Present only in A: vol-ae7ed9e3
Present only in A: vol-d9d6a8ae
Present only in A: vol-e3d6a894
Present only in A: vol-ff52deb2

ou même:

for i in ${!b[@]};do
    [ ${c[$i]} -eq 2 ] && printf "Present in both A and B: vol-%8x\n" $i
  done

sera réimprimer :

Present in both A and B: vol-175a3b54
Present in both A and B: vol-71600106
Present in both A and B: vol-98c2bbef
F. Hauri
la source
Bien sûr, si les Duplicatelignes sont inutiles, elles pourraient simplement être supprimées.
F.Hauri