Intersezione di due matrici in BASH


12

Ho due array come questo:

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)

Le matrici non sono ordinate e potrebbero contenere elementi duplicati.

  1. Vorrei creare l'intersezione di questi due array e memorizzare gli elementi in un altro array. Come potrei farlo?

  2. Inoltre, come ottengo l'elenco degli elementi che appaiono in B e non sono disponibili in A?


2
Usa un vero linguaggio di programmazione, non una shell per questo tipo di attività.
Stéphane Chazelas,

1
Devi mantenere l'ordine degli elementi? Se ci sono elementi duplicati (ad esempio, A e B contengono entrambi foodue volte), è necessario duplicarli nel risultato?
Gilles 'SO-smetti di essere malvagio' il

Risposte:


13

comm(1)è uno strumento che confronta due elenchi e può darti l'intersezione o la differenza tra due elenchi. Gli elenchi devono essere ordinati, ma è facile da raggiungere.

Per ottenere le matrici in un elenco ordinato adatto a comm:

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

Ciò trasformerà l'array A in un elenco ordinato. Fai lo stesso per B.

Per utilizzare commper restituire l'intersezione:

$ comm -1 -2 file1 file2

-1 -2 dice di rimuovere le voci univoche per file1 (A) e uniche per file2 (B) - l'intersezione delle due.

Per farlo restituire ciò che è in file2 (B) ma non file1 (A):

$ comm -1 -3 file1 file2

-1 -3 dice di rimuovere le voci univoche per file1 e comuni ad entrambi - lasciando solo quelle uniche per file2.

Per alimentare due pipeline in comm, utilizzare la funzione "Sostituzione processo" di bash:

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

Per acquisire questo in un array:

$ C=($(command))

Mettere tutto insieme:

# 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)))

Funzionerà solo se i tuoi valori non contengono \n.
Chris Down,

@ChrisDown: Esatto. Cerco sempre di scrivere script di shell correttamente quotati e di gestire tutti i caratteri, ma ho rinunciato a \ n. Non l'ho MAI visto in un nome file e un gran numero di strumenti unix funzionano con \ n record delimitati che perdi molto se tenti di gestire \ n come carattere valido.
Camh,

1
L'ho visto nei nomi dei file quando si utilizzano i file manager della GUI che non disinfettano correttamente i nomi dei file di input che vengono copiati da qualche altra parte (inoltre, nessuno ha detto nulla sui nomi dei file).
Chris Down,

Per proteggere \nprova questo: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

Non si dovrebbe impostare LC_ALL=C. Invece, imposta LC_COLLATE=Clo stesso guadagno in termini di prestazioni senza altri effetti collaterali. Per ottenere risultati corretti dovrai anche impostare la stessa fascicolazione commutilizzata per sort, ad esempio:unset LC_ALL; LC_COLLATE=C ; comm -12 <(printf '%s\n' "${A[@]}" | sort) <(printf '%s\n' "${B[@]}" | sort)
Sorpigal

4

Puoi ottenere tutti gli elementi che si trovano sia in A che in B eseguendo il ciclo tra gli array e confrontando:

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[@]}"

Puoi ottenere tutti gli elementi in B ma non in A in un modo simile:

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[@]}"

Esercizio: se si scambia Ae B, è intersectionssempre lo stesso fino al riordino?
Gilles 'SO- smetti di essere malvagio' il

@Gilles Se le matrici possono contenere elementi duplicati, no.
Chris Down,

3

Esiste un approccio piuttosto elegante ed efficiente per farlo, usando uniq- ma dovremo eliminare i duplicati da ciascun array, lasciando solo elementi unici. Se si desidera salvare i duplicati, esiste un solo modo "eseguendo il ciclo tra gli array e il confronto".

Considera che abbiamo due array:

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)

Prima di tutto, consente di trasformare questi array in set. Faremo perché c'è matematico intersezione operazione che è noto come intersezione di insiemi e set è un insieme di diversi oggetti, distinte o uniche . Ad essere sincero, non so cosa sia "intersezione" se parliamo di elenchi o sequenze. Sebbene possiamo scegliere una sottosequenza dalla sequenza, ma questa operazione (selezione) ha un significato leggermente diverso.

Quindi, trasformiamo!

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

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

    Se si desidera memorizzare gli elementi in un altro array:

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

    uniq -dsignifica mostrare solo i duplicati (penso, uniqè piuttosto veloce a causa della sua realizzazione: immagino che sia fatto con l' XORoperazione).

  2. Ottieni l'elenco di elementi che appaiono Be non sono disponibili in A, ad esB\A

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

    Oppure, con il salvataggio in una variabile:

    $ 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
    

    Quindi, all'inizio abbiamo un'intersezione di Ae B(che è semplicemente l'insieme di duplicati tra loro), diciamo che lo è A/\B, e quindi abbiamo usato l'operazione di invertire l'intersezione di Be A/\B(che è semplicemente solo elementi unici), quindi otteniamo B\A = ! (B /\ (A/\B)).

PS è uniqstato scritto da Richard M. Stallman e David MacKenzie.


1

Ignorando l'efficienza, ecco un approccio:

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[@]}"

0

Il mio modo puro bash

Dato che queste variabili contengono solo vol-XXXdove XXXc'è un numero esadecimale, c'è un modo rapido usando gli array 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

Questo deve produrre:

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

In questo stato, l'ambiente bash contiene:

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")

Quindi potresti:

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

Questo renderà:

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

Ma questo è numericamente ordinato! Se desideri un ordine originale, puoi:

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

Quindi visualizzi vols nello stesso ordine in cui sono stati inviati:

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

o

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

per mostrare solo in 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

o anche:

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

sarà ri-stampa :

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

Naturalmente, se le Duplicatelinee sono inutili, potrebbero semplicemente essere eliminate.
F. Hauri,
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.