Schnittpunkt zweier Arrays in BASH

12

Ich habe zwei Arrays wie folgt:

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)

Die Arrays sind nicht sortiert und enthalten möglicherweise sogar doppelte Elemente.

  1. Ich möchte den Schnittpunkt dieser beiden Arrays erstellen und die Elemente in einem anderen Array speichern. Wie würde ich das machen?

  2. Wie erhalte ich die Liste der Elemente, die in B angezeigt werden und in A nicht verfügbar sind?

Bogdan
quelle
2
Verwenden Sie eine echte Programmiersprache, keine Shell für diese Art von Aufgabe.
Stéphane Chazelas
1
Müssen Sie die Reihenfolge der Elemente beibehalten? Wenn es doppelte Elemente gibt (z. B. A und B enthalten beide foozweimal), müssen Sie sie im Ergebnis duplizieren?
Gilles 'SO - hör auf böse zu sein'

Antworten:

13

comm(1)ist ein Tool, mit dem Sie zwei Listen vergleichen und den Schnittpunkt oder Unterschied zwischen zwei Listen ermitteln können. Die Listen müssen sortiert werden, aber das ist einfach zu erreichen.

Um Ihre Arrays in eine sortierte Liste zu bringen, die geeignet ist für comm:

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

Dadurch wird Array A in eine sortierte Liste umgewandelt. Machen Sie dasselbe für B.

So commgeben Sie die Kreuzung zurück:

$ comm -1 -2 file1 file2

-1 -2 sagt, Einträge zu entfernen, die für Datei1 (A) und für Datei2 (B) eindeutig sind - der Schnittpunkt der beiden.

Damit es zurückgibt, was sich in Datei2 (B) befindet, aber nicht in Datei1 (A):

$ comm -1 -3 file1 file2

-1 -3 Sagt, Einträge zu entfernen, die für Datei1 eindeutig und für beide gemeinsam sind - wobei nur die Einträge für Datei2 eindeutig bleiben.

commVerwenden Sie zum Einspeisen von zwei Pipelines die Funktion "Substitution verarbeiten" von bash:

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

So erfassen Sie dies in einem Array:

$ C=($(command))

Alles zusammenfassen:

# 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
quelle
Dies funktioniert nur, wenn Ihre Werte nicht enthalten \n.
Chris Down
@ ChrisDown: Das stimmt. Ich versuche immer, Shell-Skripte zu schreiben, die korrekt zitiert sind und mit allen Zeichen umgehen, aber ich habe es aufgegeben, \ n. Ich habe es NIE in einem Dateinamen gesehen, und eine große Menge von Unix-Tools arbeiten mit \ n durch Trennzeichen getrennten Datensätzen, die Sie viel verlieren, wenn Sie versuchen, \ n als gültiges Zeichen zu behandeln.
13.12.13
1
Ich habe es in Dateinamen gesehen, wenn ich GUI-Dateimanager verwende, die Eingabedateinamen, die von einem anderen Ort kopiert wurden, nicht ordnungsgemäß bereinigen (außerdem hat niemand etwas über Dateinamen gesagt).
Chris Down
Zum Schutz \nversuchen Sie Folgendes: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
Man sollte nicht einstellen LC_ALL=C. Stellen Sie stattdessen LC_COLLATE=Cden gleichen Leistungszuwachs ohne andere Nebenwirkungen ein. Um korrekte Ergebnisse zu erhalten , müssen Sie auch die gleiche Sortierung einstellen, die für commverwendet wurde sort, z. B .:unset LC_ALL; LC_COLLATE=C ; comm -12 <(printf '%s\n' "${A[@]}" | sort) <(printf '%s\n' "${B[@]}" | sort)
Sorpigal
4

Sie können alle Elemente in A und B abrufen, indem Sie beide Arrays durchlaufen und Folgendes vergleichen:

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

Sie können alle Elemente in B, aber nicht in A auf ähnliche Weise abrufen:

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
quelle
Übung: Wenn Sie tauschen Aund B, ist es intersectionsimmer das Gleiche, um neu zu ordnen?
Gilles 'SO - hör auf böse zu sein'
@Gilles Wenn die Arrays doppelte Elemente enthalten dürfen, nein.
Chris Down
3

Dafür gibt es einen ziemlich eleganten und effizienten Ansatz uniq wir - verwenden. Wir müssen jedoch Duplikate aus jedem Array entfernen, sodass nur eindeutige Elemente übrig bleiben. Wenn Sie Duplikate speichern möchten, gibt es nur eine Möglichkeit, "beide Arrays zu durchlaufen und zu vergleichen".

Stellen Sie sich vor, wir haben zwei Arrays:

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)

Lassen Sie uns zunächst diese Arrays in Mengen umwandeln. Wir werden es tun, weil es eine mathematische Operationskreuzung gibt, die als Schnittmenge von Mengen bekannt ist, und Menge ist eine Sammlung verschiedener Objekte, verschiedene oder einzigartig . Um ehrlich zu sein, ich weiß nicht, was "Schnittmenge" ist, wenn wir über Listen oder Sequenzen sprechen. Wir können zwar eine Teilsequenz aus der Sequenz auswählen, aber diese Operation (Auswahl) hat eine etwas andere Bedeutung.

Also, lasst uns verwandeln!

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

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

    Wenn Sie die Elemente in einem anderen Array speichern möchten:

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

    uniq -dAlso zeige nur Duplikate (ich denke, uniqist aufgrund seiner Realisierung eher schnell: Ich denke, dass es mit erledigt istXOR Bedienung ).

  2. Rufen Sie die Liste der Elemente ab, die in angezeigt werden Bund in nicht verfügbar sind A, zB\A

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

    Oder beim Speichern in einer Variablen:

    $ 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

    Also haben wir zuerst den Schnittpunkt von Aund B(das ist einfach die Menge der Duplikate zwischen ihnen), sagen wir es ist A/\B, und dann haben wir die Operation des Invertierens des Schnittpunkts von Bund A/\B(das ist einfach nur ein einziges Element) verwendet, also bekommen wir B\A = ! (B /\ (A/\B)).

PS uniqwurde von Richard M. Stallman und David MacKenzie geschrieben.

Kenichi
quelle
1

Effizienz ignorieren, hier ist ein Ansatz:

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
quelle
0

Mein reiner Bash-Weg

Da diese Variablen nur enthalten , vol-XXXwenn XXXeine hexadezimale Zahl ist, gibt es eine schnelle Möglichkeit , mit bash - Arrays

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

Dies muss Folgendes ausgeben:

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

In diesem Zustand enthält die Bash-Umgebung:

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

Sie könnten also:

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

Dies ergibt:

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

Dies ist aber numerisch sortiert! Wenn Sie eine Originalbestellung wünschen, können Sie:

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

Sie zeigen die Bände in der Reihenfolge an, in der sie eingereicht wurden:

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

oder

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

für die Anzeige nur 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

oder auch:

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

wird erneut drucken :

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
quelle
Wenn DuplicateLinien nutzlos sind, können sie natürlich einfach fallengelassen werden.
F. Hauri