Giao điểm của hai mảng trong BASH


12

Tôi có hai mảng như thế này:

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)

Các mảng không được sắp xếp và thậm chí có thể chứa các phần tử trùng lặp.

  1. Tôi muốn tạo giao điểm của hai mảng này và lưu trữ các phần tử trong một mảng khác. Làm thế nào tôi có thể làm điều đó?

  2. Ngoài ra, làm thế nào tôi có được danh sách các yếu tố xuất hiện trong B và không có sẵn trong A?


2
Sử dụng một ngôn ngữ lập trình thực sự, không phải là một vỏ cho loại nhiệm vụ này.
Stéphane Chazelas

1
Bạn có cần giữ lại thứ tự của các yếu tố? Nếu có các phần tử trùng lặp (ví dụ A và B đều chứa foohai lần), bạn có cần chúng trùng lặp trong kết quả không?
Gilles 'SO- đừng trở nên xấu xa'

Câu trả lời:


13

comm(1)là một công cụ so sánh hai danh sách và có thể cung cấp cho bạn giao điểm hoặc sự khác biệt giữa hai danh sách. Các danh sách cần được sắp xếp, nhưng điều đó dễ dàng đạt được.

Để đưa các mảng của bạn vào một danh sách được sắp xếp phù hợp với comm:

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

Điều đó sẽ biến mảng A thành một danh sách được sắp xếp. Làm tương tự cho B.

Để sử dụng commđể trả lại giao lộ:

$ comm -1 -2 file1 file2

-1 -2 nói để xóa các mục duy nhất cho tệp1 (A) và duy nhất cho tệp2 (B) - giao điểm của hai.

Để có nó trả về những gì trong tệp2 (B) nhưng không phải tệp1 (A):

$ comm -1 -3 file1 file2

-1 -3 nói để xóa các mục duy nhất cho tệp1 và chung cho cả hai - chỉ để lại các mục duy nhất cho tệp2.

Để đưa hai đường ống vào comm, sử dụng tính năng "Thay thế quy trình" của bash:

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

Để nắm bắt điều này trong một mảng:

$ C=($(command))

Để tất cả chúng cùng nhau:

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

Điều này sẽ chỉ hoạt động nếu giá trị của bạn không chứa \n.
Chris Xuống

@ChrisDown: Đúng vậy. Tôi luôn cố gắng viết các kịch bản shell được trích dẫn chính xác và xử lý tất cả các ký tự, nhưng tôi đã từ bỏ \ n. Tôi KHÔNG BAO GIỜ thấy nó trong một tên tệp và một loạt lớn các công cụ unix hoạt động với các bản ghi được phân tách bằng \ n mà bạn mất rất nhiều nếu bạn cố xử lý \ n như một char hợp lệ.
camh

1
Tôi đã thấy nó trong tên tệp khi sử dụng trình quản lý tệp GUI không vệ sinh đúng tên tệp đầu vào được sao chép từ một nơi khác (ngoài ra, không ai nói gì về tên tệp).
Chris Xuống

Để bảo vệ \nhãy thử điều này: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

Không nên đặt LC_ALL=C. Thay vào đó, thiết lập LC_COLLATE=Ccho cùng một hiệu suất đạt được mà không có tác dụng phụ khác. Để có được kết quả chính xác, bạn cũng cần đặt đối chiếu tương tự cho mục commđích đã được sử dụng cho sort, ví dụ:unset LC_ALL; LC_COLLATE=C ; comm -12 <(printf '%s\n' "${A[@]}" | sort) <(printf '%s\n' "${B[@]}" | sort)
Sorpigal

4

Bạn có thể nhận được tất cả các phần tử có trong cả A và B bằng cách lặp qua cả hai mảng và so sánh:

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ạn có thể nhận được tất cả các yếu tố trong B nhưng không phải trong A theo cách tương tự:

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

Bài tập: nếu bạn trao đổi AB, intersectionsluôn luôn giống nhau để sắp xếp lại?
Gilles 'SO- đừng trở nên xấu xa'

@Gilles Nếu các mảng có thể chứa các phần tử trùng lặp, không.
Chris Xuống

3

Có một cách tiếp cận khá thanh lịch và hiệu quả để làm điều đó, bằng cách sử dụng uniq- nhưng, chúng ta sẽ cần loại bỏ các bản sao khỏi mỗi mảng, chỉ để lại các mục duy nhất. Nếu bạn muốn lưu các bản sao, chỉ có một cách "bằng cách lặp qua cả hai mảng và so sánh".

Hãy xem xét chúng tôi có hai mảng:

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)

Trước hết, hãy biến đổi các mảng này thành các tập hợp. Chúng tôi sẽ làm điều đó bởi vì có ngã phép toán được gọi như giao điểm của bộ, và thiết lập là một tập hợp các biệt đối tượng, riêng biệt hay độc đáo . Thành thật mà nói, tôi không biết "giao lộ" là gì nếu chúng ta nói về danh sách hoặc trình tự. Mặc dù chúng ta có thể chọn ra một chuỗi từ chuỗi, nhưng thao tác (lựa chọn) này có ý nghĩa hơi khác nhau.

Vì vậy, hãy biến đổi!

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

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

    Nếu bạn muốn lưu trữ các phần tử trong một mảng khác:

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

    uniq -dcó nghĩa là chỉ hiển thị các bản sao (tôi nghĩ, uniqkhá nhanh vì nhận ra nó: tôi đoán rằng nó được thực hiện với XORhoạt động).

  2. Lấy danh sách các yếu tố xuất hiện Bvà không có sẵn A, tức làB\A

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

    Hoặc, với việc lưu trong một biến:

    $ 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

    Do đó, lúc đầu, chúng ta có giao điểm AB(đơn giản là tập hợp trùng lặp giữa chúng) A/\B, và sau đó chúng ta đã sử dụng thao tác đảo ngược giao nhau BA/\B(chỉ đơn giản là các yếu tố duy nhất), vì vậy chúng ta có được B\A = ! (B /\ (A/\B)).

PS uniqđược viết bởi Richard M. Stallman và David MacKenzie.


1

Bỏ qua hiệu quả, đây là một cách tiếp cận:

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

Cách bash tinh khiết của tôi

Như biến này chứa chỉ vol-XXXnơi XXXlà một số thập lục phân, có một cách nhanh chóng sử dụng các mảng 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

Điều này phải xuất ra:

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

Ở trạng thái này, môi trường bash của bạn chứa:

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

Vì vậy, bạn có thể:

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

Điều này sẽ kết xuất:

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

Nhưng đây là số được sắp xếp! Nếu bạn muốn đặt hàng ban đầu, bạn có thể:

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

Vì vậy, bạn loại bỏ vols theo thứ tự như đã gửi:

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

hoặc là

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

chỉ hiển thị trong 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

hoặc thậm chí:

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

sẽ in lại :

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

Tất nhiên, nếu Duplicatecác dòng là vô dụng, đơn giản là chúng có thể bị loại bỏ.
F. Hauri
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.