tech.guitarrapc.cóm

Technical updates

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

 余り本件に関して書くことが無いのですが…一応簡単なサンプルを。 今回は、時々アレっ?って自分で思うため、テキストに含まれるかどうか/比較を見る方法のメモです。 ====

サンプル

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

"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が含まれるかをCase Sensitiveに判定します。

-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は、Case Sensitiveに判定しませんでした。 Case Sencitiveに判定するには、-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は、Case Sensitiveに判定しませんでした。 Case Sencitiveに判定するには、-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は、Case Sensitiveに判定しませんでした。 Case Sencitiveに判定するには、-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が含まれるかをCase Sensitive判定します。

-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.

動的型付け、というのがポイントといえるようです。(PowerShellは、左オペラントの型に右オペラントをキャストしようとします) ……しかし、 'ー' と '々' が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 を比較する。 基本的には値比較だが、参照比較になりうる。 特に、PowerShellは左オペランドの型に左右されるため注意。 また、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は、Case Sensitiveに判定しませんでした。 Case Sencitiveに判定するには、-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が一致するかをCase Sensitiveに判定します。

[Object]::ReferenceEquals()

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

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

結果はこうですね

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

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

まとめ

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

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

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

参考

Windows PowerShell Version 3 Simplified Syntax Does -eq keyword in power shell test reference equality or use Object.Equals() Part-5: Text Manipulation in PowerShell using .Contains() and .Equals() methods