Persimpangan dua array di BASH

12

Saya punya dua array seperti ini:

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)

Array tidak diurutkan dan bahkan mungkin mengandung elemen duplikat.

  1. Saya ingin membuat persimpangan dua array ini dan menyimpan elemen dalam array lain. Bagaimana saya melakukannya?

  2. Juga, bagaimana saya mendapatkan daftar elemen yang muncul di B dan tidak tersedia di A?

Bogdan
sumber
2
Gunakan bahasa pemrograman nyata, bukan shell untuk tugas semacam ini.
Stéphane Chazelas
1
Apakah Anda perlu mempertahankan urutan elemen? Jika ada elemen duplikat (mis. A dan B keduanya mengandung foodua kali), apakah Anda memerlukannya digandakan dalam hasilnya?
Gilles 'SANGAT berhenti menjadi jahat'

Jawaban:

13

comm(1)adalah alat yang membandingkan dua daftar dan dapat memberi Anda persimpangan atau perbedaan antara dua daftar. Daftar perlu disortir, tetapi itu mudah dicapai.

Untuk membuat array Anda ke daftar yang diurutkan cocok untuk comm:

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

Itu akan mengubah array A menjadi daftar yang diurutkan. Lakukan hal yang sama untuk B.

Untuk digunakan communtuk mengembalikan persimpangan:

$ comm -1 -2 file1 file2

-1 -2 mengatakan untuk menghapus entri yang unik untuk file1 (A) dan unik untuk file2 (B) - persimpangan keduanya.

Untuk mengembalikannya apa yang ada di file2 (B) tetapi tidak file1 (A):

$ comm -1 -3 file1 file2

-1 -3 mengatakan untuk menghapus entri yang unik untuk file1 dan umum untuk keduanya - hanya menyisakan yang unik untuk file2.

Untuk memasukkan dua saluran pipa comm, gunakan fitur "Substitusi Proses" dari bash:

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

Untuk menangkap ini dalam sebuah array:

$ C=($(command))

Menyatukan semuanya:

# 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
sumber
Ini hanya akan berfungsi jika nilai Anda tidak mengandung \n.
Chris Down
@ ChrisDown: Benar. Saya selalu mencoba menulis skrip shell yang dikutip dengan benar dan menangani semua karakter, tetapi saya sudah menyerah pada \ n. Saya belum pernah melihatnya dalam nama file, dan banyak alat unix bekerja dengan \ n catatan terbatas yang Anda kehilangan banyak jika Anda mencoba menangani \ n sebagai char yang valid.
camh
1
Saya pernah melihatnya dalam nama file ketika menggunakan manajer file GUI yang tidak membersihkan dengan benar nama file input yang disalin dari tempat lain (juga, tidak ada yang mengatakan apa pun tentang nama file).
Chris Down
Untuk melindungi \ncoba ini: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
Seseorang seharusnya tidak mengatur LC_ALL=C. Alih-alih ditetapkan LC_COLLATE=Cuntuk kenaikan kinerja yang sama tanpa efek samping lainnya. Untuk mendapatkan hasil yang benar, Anda juga perlu mengatur susunan yang sama dengan commyang digunakan untuk sort, misalnya:unset LC_ALL; LC_COLLATE=C ; comm -12 <(printf '%s\n' "${A[@]}" | sort) <(printf '%s\n' "${B[@]}" | sort)
Sorpigal
4

Anda bisa mendapatkan semua elemen yang ada di A dan B dengan mengulang melalui kedua array dan membandingkan:

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

Anda bisa mendapatkan semua elemen dalam B tetapi tidak dalam A dengan cara yang sama:

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
sumber
Latihan: jika Anda bertukar Adan B, apakah intersectionsselalu sama dengan menata ulang?
Gilles 'SANGAT berhenti menjadi jahat'
@Gilles Jika array dapat berisi elemen duplikat, no.
Chris Down
3

Ada pendekatan yang agak elegan dan efisien untuk melakukan itu, menggunakan uniq- tetapi, kita perlu menghilangkan duplikat dari setiap array, hanya menyisakan item unik. Jika Anda ingin menyimpan duplikat, hanya ada satu cara "dengan mengulang melalui kedua array dan membandingkan".

Pertimbangkan kami memiliki dua 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)

Pertama-tama, mari kita ubah array ini menjadi set. Kami akan melakukannya karena ada persimpangan operasi matematika yang terkenal seperti persimpangan set, dan set adalah kumpulan yang berbeda objek, yang berbeda atau unik . Sejujurnya, saya tidak tahu apa itu "persimpangan" jika kita berbicara tentang daftar atau urutan. Meskipun kita dapat memilih urutan berikutnya dari urutan, tetapi operasi ini (pemilihan) memiliki arti yang sedikit berbeda.

Jadi, ayo bertransformasi!

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

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

    Jika Anda ingin menyimpan elemen dalam array lain:

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

    uniq -dberarti hanya menunjukkan duplikat (saya pikir, uniqagak cepat karena realisasinya: Saya kira itu dilakukan dengan XORoperasi).

  2. Dapatkan daftar elemen yang muncul Bdan tidak tersedia di A, yaituB\A

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

    Atau, dengan menyimpan dalam variabel:

    $ 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

    Jadi, pada awalnya kita memiliki persimpangan Adan B(yang merupakan kumpulan duplikat di antara mereka), katakan demikian A/\B, dan kemudian kita menggunakan operasi persimpangan simpang dari Bdan A/\B(yang hanya merupakan elemen unik), jadi kita dapatkan B\A = ! (B /\ (A/\B)).

PS uniqditulis oleh Richard M. Stallman dan David MacKenzie.

kenichi
sumber
1

Mengabaikan efisiensi, berikut ini pendekatannya:

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

Cara bash murni saya

Karena variabel ini hanya berisi di vol-XXXmana XXXbilangan heksadesimal, ada cara cepat menggunakan bash array

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

Ini harus menghasilkan:

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

Pada kondisi ini, Anda mem-bash lingkungan mengandung:

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

Jadi kamu bisa:

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

Ini akan membuat:

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

Tapi ini diurutkan secara numerik! Jika Anda ingin pesanan asli, Anda dapat:

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

Jadi, Anda menampilkan vol dalam urutan yang sama seperti yang dikirimkan:

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

atau

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

untuk ditampilkan hanya dalam 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

atau bahkan:

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

akan dicetak ulang :

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
sumber
Tentu saja, jika Duplicategaris tidak berguna, mereka bisa saja dijatuhkan.
F. Hauri