BASHの2つの配列の交差点


12

このような2つの配列があります。

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つの配列の交差を作成し、要素を別の配列に格納したいと思います。どうすればいいですか?

  2. また、Bに表示されAで使用できない要素のリストを取得するにはどうすればよいですか?


2
この種のタスクにはシェルではなく、実際のプログラミング言語を使用してください。
ステファンシャゼル

1
要素の順序を保持する必要がありますか?重複する要素がある場合(たとえば、AとBの両方にfoo2回含まれる場合)、結果に重複する必要がありますか?
ジル 'SO-悪であるのをやめる'

回答:


13

comm(1)は、2つのリストを比較し、2つのリストの共通部分または違いを示すことができるツールです。リストはソートする必要がありますが、簡単に実現できます。

配列を以下に適したソート済みリストに入れるにはcomm

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

これにより、配列Aがソート済みリストになります。Bについても同じことを行います。

comm交差点を返すために使用するには:

$ comm -1 -2 file1 file2

-1 -2 file1に固有のエントリ(A)とfile2に固有のエントリ(B)の削除-2つの交差点。

file1(A)ではなくfile2(B)にあるものを返すようにするには:

$ comm -1 -3 file1 file2

-1 -3 file1に固有で両方に共通のエントリを削除するように指示します-file2に固有のエントリのみを残します。

に2つのパイプラインをフィードする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
クリスダウン

@ChrisDown:そうです。私は常に適切に引用され、すべての文字を処理するシェルスクリプトを作成しようとしますが、\ nをあきらめました。私はファイル名でそれを見たことがないし、unixツールの多くは、有効な文字として\ nを処理しようとすると、多くを失う\ n区切りレコードで動作します。
カム

1
他の場所からコピーされた入力ファイル名を適切にサニタイズしないGUIファイルマネージャーを使用するときにファイル名で見ました(また、ファイル名については誰も言いませんでした)。
クリスダウン

これを\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
ジェイソンR.ミック

設定しないでください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した場合、並べ替えまでBintersections常に同じですか?
ジル 'SO-悪

@Gilles配列に重複した要素が含まれる場合、いいえ。
クリスダウン

3

使用するには、エレガントで効率的なアプローチがありますが、uniq各配列から重複を排除し、一意のアイテムのみを残す必要があります。重複を保存する場合、「両方の配列をループして比較する」という方法が1つしかありません。

2つの配列があるとします。

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)

まず、これらの配列をセットに変換します。セットの交差点のように知られている数学的操作の交差点があり、セットはdistinctまたはuniqueの異なるオブジェクトのコレクションであるため、これを行います。正直に言うと、リストやシーケンスについて話す場合、「交差点」とは何なのかわかりません。シーケンスからサブシーケンスを選択することもできますが、この操作(選択)の意味は少し異なります。

だから、変身しましょう!

$ 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

    したがって、最初にAand B(これは単にそれらの間の重複のセットです)の交点を取得し、それがA/\Bであると言ってから、Band A/\B(単に一意の要素のみ)の交点を反転する演算を使用しましたB\A = ! (B /\ (A/\B))

PS uniqはリチャード・M・ストールマンとデビッド・マッケンジーによって書かれました。


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

私の純粋なbash方法

この変数には16進数のvol-XXX位置のみが含まれるため、bash配列XXXを使用する簡単な方法があります

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

この状態では、bash環境には次のものが含まれます。

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

したがって、送信されたのと同じ順序でvolを破棄します。

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.ハウリ
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.