tech.guitarrapc.cóm

Technical updates

PowerShellで文字列の比較をする際のTips

http://www.leeholmes.com/blog/2007/12/05/powershells-eq-operator-reference-equality-vs-value-equality/余り本件に関して書くことが無いのですが、一応簡単なサンプルを。

今回は、テキストに含まれるかどうか/比較を見る方法のメモです。

サンプル

今回、これらを試してみます。

"abc".Contains("a")
"Abc".Contains("a")
""
"abc" -contains "a"
@("abc","bc","c") -contains "a"
@("abc","bc","c") -contains "c"
@("abc","bc","C") -contains "c"
""
"abc" -ccontains "a"
@("abc","bc","c") -ccontains "a"
@("abc","bc","c") -ccontains "c"
@("abc","bc","C") -ccontains "c"
""
"a" -in "abc"
"a" -in @("abc","bc","c")
"c" -in @("abc","bc","c")
"c" -in @("abc","bc","C")
""
"a" -cin "abc"
"a" -cin @("abc","bc","c")
"c" -cin @("abc","bc","c")
"c" -cin @("abc","bc","C")
""
"abc" -match "a"
"abc" -match "A"
"abc" -cmatch "a"
"abc" -cmatch "A"
""
[regex]::Match("abc","a")
""
"abc" -eq "a"
"a" -eq "a"
"A" -eq "a"
""
"abc" -ceq "a"
"a" -ceq "a"
"A" -ceq "a"
""
"abc".Equals("a")
"a".Equals("a")
"a".Equals("A")
""
'ー'.Contains('々')
'ー' -contains '々'
'ー' -ccontains '々'
'々' -in 'ー'
'々' -cin 'ー'
'ー' -match '々'
'ー' -cmatch '々'
'ー' -eq '々'
'ー' -ceq '々'
'ー'.Equals('々')
[Object]::ReferenceEquals('ー', '々')

結果を見てみましょう。

True #"abc".Contains("a")
False #"Abc".Contains("a")

False #"abc" -contains "a"
False #@("abc","bc","c") -contains "a"
True #@("abc","bc","c") -contains "c"
True #@("abc","bc","C") -contains "c"

False #"abc" -ccontains "a"
False #@("abc","bc","c") -ccontains "a"
True #@("abc","bc","c") -ccontains "c"
False #@("abc","bc","C") -ccontains "c"

False #"a" -in "abc"
False #"a" -in @("abc","bc","c")
True #"c" -in @("abc","bc","c")
True #"c" -in @("abc","bc","C")

False #"a" -cin "abc"
False #"a" -cin @("abc","bc","c")
True #"c" -cin @("abc","bc","c")
False #"c" -cin @("abc","bc","C")

True #"abc" -match "a"
True #"abc" -match "A"
True #"abc" -cmatch "a"
False #"Abc" -cmatch "A"

#[regex]::Match("abc","a")
Groups   : {a}
Success  : True
Captures : {a}
Index    : 0
Length   : 1
Value    : a


False #"abc" -eq "a"
True #"a" -eq "a"
True #"A" -eq "a"

False #"abc" -ceq "a"
True #"a" -ceq "a"
False #"A" -ceq "a"

False #"abc".Equals("a")
True #"a".Equals("a")
False #"a".Equals("A")

False #'ー'.Contains('々')
True #'ー' -contains '々'
True #'ー' -ccontains '々'
True #'々' -in 'ー'
True #'々' -cin 'ー'
False #'ー' -match '々'
False #'ー' -cmatch '々'
True #'ー' -eq '々'
True #'ー' -ceq '々'
False #'ー'.Equals('々')
False #[Object]::ReferenceEquals('ー', '々')

それぞれ見てみます。

contains()

このようなコードでした。

"abc".Contains("a")
"Abc".Contains("a")
'ー'.Contains('々')

結果はこうですね

True #"abc".Contains("a")
False #"Abc".Contains("a")
False #'ー'.Contains('々')

動作は、そのままです。"Value".Contrains(x)の時、「Valueにxが含まれるか」をケースセンシティブに判定します。

-contains

このようなコードでした。

"abc" -contains "a"
@("abc","bc","c") -contains "a"
@("abc","bc","c") -contains "c"
@("abc","bc","C") -contains "c"
'ー' -contains '々'

結果はこうですね

False #"abc" -contains "a"
False #@("abc","bc","c") -contains "a"
True #@("abc","bc","c") -contains "c"
True #@("abc","bc","C") -contains "c"
True #'ー' -contains '々'

.Contains()と異なるのが分かります。 @("Value1","Value2","Value3") -Contrains "x"の時、xが配列の要素"Value1"と"Value2"に含まれるかを判定します。

この判定は、配列要素との判定であり、配列の構成文字ではありません。 つまり、Value1="abc"x="a"の場合は「"abc"に"a"が含まれるかどうかの判定ではありません」。.Contains()が、"abc"と"a"の場合は「"abc"に含まれるかどうか判定」であるため、.Contains()-containsでは動作が違います。

-ccontains

このようなコードでした。

"abc" -ccontains "a"
@("abc","bc","c") -ccontains "a"
@("abc","bc","c") -ccontains "c"
@("abc","bc","C") -ccontains "c"
'ー' -ccontains '々'

結果はこうですね

False #"abc" -ccontains "a"
False #@("abc","bc","c") -ccontains "a"
True #@("abc","bc","c") -ccontains "c"
False #@("abc","bc","C") -ccontains "c"
True #'ー' -ccontains '々'

-containsは、ケースセンシティブに判定しませんでした。 ケースセンシティブに判定するには、-ccontainsとします。

-in

このようなコードでした。

"a" -in "abc"
"a" -in @("abc","bc","c")
"c" -in @("abc","bc","c")
"c" -in @("abc","bc","C")
'々' -in 'ー'

結果はこうですね

False #"a" -in "abc"
False #"a" -in @("abc","bc","c")
True #"c" -in @("abc","bc","c")
True #"c" -in @("abc","bc","C")
True #'々' -in 'ー'
False #[Object]::ReferenceEquals('ー', '々')

-inは、PowerShell 3.0から追加されました。 動作は、-containsのOperatorが逆になったものです。 "x" -in @("Value1","Value2","Value3") の時、xが配列の要素"Value1"と"Value2"に含まれるかを判定します。-containsに比べて、右辺と左辺が入れ替わっていることが分かります。

-cin

このようなコードでした。

"a" -cin "abc"
"a" -cin @("abc","bc","c")
"c" -cin @("abc","bc","c")
"c" -cin @("abc","bc","C")
'々' -cin 'ー'

結果はこうですね

False #"a" -cin "abc"
False #"a" -cin @("abc","bc","c")
True #"c" -cin @("abc","bc","c")
False #"c" -cin @("abc","bc","C")
True #'々' -cin 'ー'

-inは、ケースセンシティブに判定しませんでした。 ケースセンシティブに判定するには、-cinとします。

-match

このようなコードでした。

"abc" -match "a"
"abc" -match "A"
'ー' -match '々'

結果はこうですね

True #"abc" -match "a"
True #"Abc" -cmatch "a"
False #'ー' -match '々'

動作はそのままです。"Value" -match "x"の時、正規表現でValueにxが含まれるかを判定します。

-cmatch

このようなコードでした。

"abc" -cmatch "a"
"abc" -cmatch "A"
'ー' -cmatch '々'

結果はこうですね

True #"abc" -cmatch "a"
False #"abc" -cmatch "A"
False #'ー' -cmatch '々'

-matchはケースセンシティブに判定しませんでした。 ケースセンシティブに判定するには、-cmatchとします。

[regex]::Match()

このようなコードでした。

[regex]::Match("abc","a")

結果はこうですね

#[regex]::Match("abc","a")
Groups   : {a}
Success  : True
Captures : {a}
Index    : 0
Length   : 1
Value    : a

動作は、そのままです。[regex]::Match("Value","x")の時、正規表現でValueにxが含まれるかをケースセンシティブ判定します。

-eq

このようなコードでした。

"abc" -eq "a"
"a" -eq "a"
"A" -eq "a"
'ー' -eq '々'

結果はこうですね

False #"abc" -eq "a"
True #"a" -eq "a"
True #"A" -eq "a"
True #'ー' -eq '々'

動作は、そのままと思いきや、'ー''々'がTrueはトラップになりそう。

少し見てみましょう。ここによると、基本的には参照比較ではなく値比較ですが、SecureStringのように参照比較しかクラスにない場合は参照比較を行うようです。

PowerShell’s -EQ Operator: Reference Equality vs Value Equality

そしてここで詳しく解説されています。

PowerShell equality operator not a symmetric relation?

この理解が必要なようです。

-eq in PowerShell is not an equivalence relation.

動的型付けというのがポイントといえるようです。'ー''々'がTrueなのはbooleanにも癖があるようです。 これはどうなるでしょうか。

[ConsoleColor]::Black -eq $true
$true -eq [ConsoleColor]::Black

結果です。

False
True

順序を入れかえると結果が変わりました。 $nullに対しても、同様の癖があるようです。 配列時の-eqのフィルタ挙動と、$nullが含まれる際の動作については、牟田口先生の解説が参考になります。

if($array -eq $null) には要注意!

$nullに関しては、「分からない」を示すものと考えればよさそうですね。 これについては、SQLの記事ですがbleis先生が参考になります。

ぐるぐる~ - SQLアンチパターン

  • Value -eq xの時に、Valueとxを比較する
  • 基本的には値比較だが、参照比較になりうる
  • boolean/null判定する場合は、$true -eq Valueあるいは$null -eq Valueのように順序に気を付ける
  • 配列で利用した場合にフィルタ動作する

-ceq

このようなコードでした。

"abc" -ceq "a"
"a" -ceq "a"
"A" -ceq "a"
'ー' -ceq '々'

結果はこうですね

False #"abc" -ceq "a"
True #"a" -ceq "a"
False #"A" -ceq "a"
'ー' -ceq '々'

-eqは、ケースセンシティブに判定しませんでした。 ケースセンシティブに判定するには、-ceqとします。

.Equals()

このようなコードでした。

"abc".Equals("a")
"a".Equals("a")
"a".Equals("A")
'ー'.Equals('々')

結果はこうですね

False #"abc".Equals("a")
True #"a".Equals("a")
False #"a".Equals("A")
False #'ー'.Equals('々')

動作は、そのままですね。あー、良かった。 "Value".Equals(x) の時、Valueとxが一致するかをケースセンシティブに判定します。

[Object]::ReferenceEquals()

このようなコードでした。

[Object]::ReferenceEquals('ー', '々')

結果はこうですね

False #[Object]::ReferenceEquals('ー', '々')

動作は、そのままですね。[Object]::ReferenceEquals(Value, x)の時、Valueとxの参照先が一致するかを判定します。

まとめ

単純に文字列を比較するなら…この辺が適してそうですね。

.Contains()
.Equals()
-match
[regex]::Match()

オブジェクトの比較時には -eqを頻繁に利用しますが、少し注意をした方がいいようです。

参考