จุดตัดของสองอาร์เรย์ใน BASH


12

ฉันมีสองอาร์เรย์ดังนี้:

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)

อาร์เรย์ไม่ถูกเรียงลำดับและอาจมีองค์ประกอบที่ซ้ำกัน

  1. ฉันต้องการสร้างจุดตัดของทั้งสองอาร์เรย์และเก็บองค์ประกอบไว้ในอาร์เรย์อื่น ฉันจะทำอย่างไร

  2. นอกจากนี้ฉันจะดูรายการองค์ประกอบที่ปรากฏใน B และไม่มีใน A ได้อย่างไร


2
ใช้ภาษาการเขียนโปรแกรมจริงไม่ใช่เชลล์สำหรับงานประเภทนี้
Stéphane Chazelas

1
คุณต้องการรักษาลำดับขององค์ประกอบหรือไม่? หากมีองค์ประกอบที่ซ้ำกัน (เช่น A และ B ทั้งสองมีfooสองครั้ง) คุณต้องการให้พวกเขาซ้ำกันในผลลัพธ์หรือไม่?
Gilles 'หยุดชั่วร้าย'

คำตอบ:


14

comm(1)เป็นเครื่องมือที่เปรียบเทียบสองรายการและสามารถให้จุดตัดหรือความแตกต่างระหว่างสองรายการ รายการจะต้องมีการเรียงลำดับ แต่มันง่ายที่จะบรรลุ

ในการทำให้อาร์เรย์ของคุณอยู่ในรายการที่เรียงเหมาะสำหรับcomm:

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

นั่นจะเปลี่ยนอาร์เรย์ A เป็นรายการที่เรียงลำดับ ทำแบบเดียวกันกับบี

วิธีใช้commเพื่อส่งคืนทางแยก:

$ comm -1 -2 file1 file2

-1 -2 บอกว่าจะลบรายการที่ไม่ซ้ำกับ file1 (A) และเฉพาะกับ file2 (B) - จุดตัดของทั้งสอง

หากต้องการให้มันส่งคืนสิ่งที่อยู่ใน file2 (B) แต่ไม่ใช่ file1 (A):

$ comm -1 -3 file1 file2

-1 -3 บอกว่าจะลบรายการที่ไม่ซ้ำกับ file1 และใช้ร่วมกันกับทั้งคู่ - ปล่อยเฉพาะรายการที่ไม่ซ้ำกับ file2

ในการฟีดไปป์ไลน์สองท่อให้commใช้คุณสมบัติ "การทดแทนกระบวนการ" ของbash:

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

ในการจับภาพในอาเรย์:

$ C=($(command))

วางมันทั้งหมดเข้าด้วยกัน:

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

\nนี้จะทำงานถ้าค่าของคุณไม่ได้มี
Chris Down

@ChrisDown: ถูกต้อง ฉันมักจะพยายามเขียนเชลล์สคริปท์ที่อ้างอิงอย่างเหมาะสมและจัดการกับตัวอักษรทั้งหมด แต่ฉันยอมแพ้ใน \ n ฉันไม่เคยเห็นมันมาในชื่อไฟล์และมีเครื่องมือยูนิกซ์มากมายที่ทำงานกับ \ n ตัวคั่นที่คุณสูญเสียไปมากถ้าคุณพยายามจัดการ \ n ให้เป็นคนที่ถูกต้อง
camh

1
ฉันเห็นมันในชื่อไฟล์เมื่อใช้ตัวจัดการไฟล์ GUI ที่ไม่ถูกต้องฆ่าชื่อไฟล์อินพุตที่คัดลอกมาจากที่อื่น (เช่นไม่มีใครพูดอะไรเกี่ยวกับชื่อไฟล์)
Chris Down

เพื่อปกป้อง\nสิ่งนี้ลองทำดู: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

ไม่ควรตั้งค่าLC_ALL=Cอย่างใดอย่างหนึ่ง แทนที่จะตั้งไว้LC_COLLATE=Cเพื่อให้ได้ประสิทธิภาพเดียวกันโดยไม่มีผลข้างเคียงอื่น ๆ เพื่อให้ได้ผลลัพธ์ที่ถูกต้องคุณจะต้องตั้งค่าการเปรียบเทียบเดียวกันสำหรับcommที่ใช้sortเช่น:unset LC_ALL; LC_COLLATE=C ; comm -12 <(printf '%s\n' "${A[@]}" | sort) <(printf '%s\n' "${B[@]}" | sort)
Sorpigal

4

คุณสามารถรับองค์ประกอบทั้งหมดที่อยู่ใน A และ B ได้โดยการวนลูปผ่านทั้งสองอาร์เรย์และเปรียบเทียบ:

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

คุณสามารถรับองค์ประกอบทั้งหมดใน B แต่ไม่ได้อยู่ใน A ในลักษณะที่คล้ายกัน:

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

การใช้สิทธิ: ถ้าคุณแลกเปลี่ยนAและBเป็นintersectionsเสมอขึ้นเดียวกันกับการจัดเรียงใหม่?
Gilles 'หยุดชั่วร้าย'

@Gilles หากอาร์เรย์อาจมีองค์ประกอบที่ซ้ำกันไม่
คริสลง

3

มีวิธีการที่ค่อนข้างหรูหราและมีประสิทธิภาพในการทำเช่นนั้นโดยใช้uniq- แต่เราจะต้องกำจัดรายการที่ซ้ำกันออกจากแต่ละอาร์เรย์โดยปล่อยเฉพาะรายการที่ไม่ซ้ำกัน หากคุณต้องการบันทึกรายการที่ซ้ำกันมีเพียงวิธีเดียว "โดยวนลูปผ่านทั้งอาร์เรย์และการเปรียบเทียบ"

พิจารณาว่าเรามีสองอาร์เรย์:

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)

ก่อนอื่นให้แปลงอาร์เรย์เหล่านี้เป็นชุด เราจะทำมันเพราะมีแยกการคำนวณทางคณิตศาสตร์ซึ่งเป็นที่รู้จักกันดีเช่นจุดตัดของชุดและชุดคือชุดของที่แตกต่างกันวัตถุที่แตกต่างกันหรือไม่ซ้ำกัน พูดตามตรงฉันไม่รู้หรอกว่า "ทางแยก" คืออะไรถ้าเราพูดถึงรายการหรือลำดับ แม้ว่าเราสามารถเลือกลำดับจากลำดับ แต่การดำเนินการนี้ (ตัวเลือก) มีความหมายแตกต่างกันเล็กน้อย

ดังนั้นให้แปลง!

$ A=(echo ${A[@]} | sed 's/ /\n/g' | sort | uniq)
$ B=(echo ${B[@]} | sed 's/ /\n/g' | sort | uniq)
  1. ทางแยก:

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

    หากคุณต้องการจัดเก็บองค์ประกอบในอาร์เรย์อื่น:

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

    uniq -dหมายถึงการแสดงที่ซ้ำกันเท่านั้น (ฉันคิดว่าuniqค่อนข้างเร็วเนื่องจากการรับรู้: ฉันเดาว่ามันจะทำกับXORการดำเนินการ)

  2. รับรายการองค์ประกอบที่ปรากฏในBและไม่พร้อมใช้งานAเช่นB\A

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

    หรือด้วยการบันทึกในตัวแปร:

    $ 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
    

    ดังนั้นในตอนแรกเราได้มีจุดตัดของAและB(ซึ่งก็คือชุดของรายการที่ซ้ำกันระหว่างพวกเขา) บอกว่ามันเป็นA/\Bแล้วเราใช้ในการดำเนินงานของ inverting สี่แยกBและA/\B(ซึ่งเป็นเพียงแค่เพียงองค์ประกอบที่ไม่ซ้ำกัน) B\A = ! (B /\ (A/\B))ดังนั้นเราได้รับ

PS uniqเขียนโดย Richard M. Stallman และ David MacKenzie


1

ไม่สนใจประสิทธิภาพนี่เป็นวิธีการ:

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

วิธีทุบตีบริสุทธิ์ของฉัน

เนื่องจากตัวแปรนี้มีเฉพาะvol-XXXที่XXXเป็นเลขฐานสิบหกจึงมีวิธีที่รวดเร็วในการใช้อาร์เรย์ 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

สิ่งนี้จะต้องส่งออก:

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

ที่สถานะนี้คุณทุบตีสภาพแวดล้อมประกอบด้วย:

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

ดังนั้นคุณสามารถ:

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

สิ่งนี้จะทำให้:

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

แต่นี่เป็นตัวเลขเรียง! ถ้าคุณต้องการคำสั่งเดิมคุณสามารถ:

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

ดังนั้นคุณจึงต้องวางโวส์ในแบบเดียวกับที่ส่ง

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

หรือ

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

สำหรับแสดงเฉพาะใน 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

หรือแม้กระทั่ง:

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

จะพิมพ์อีกครั้ง :

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

แน่นอนถ้าDuplicateเส้นไม่มีประโยชน์พวกเขาก็อาจจะลดลง
F. Hauri
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.