Last active
June 26, 2026 07:18
-
-
Save aetos382/9afcb1eebb241ec62905ed60d9ef9130 to your computer and use it in GitHub Desktop.
MSBuild の文字列エスケープはわけわからん
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <Project DefaultTargets="Show"> | |
| <PropertyGroup> | |
| <!-- リテラルとして ; を書くとエスケープされない --> | |
| <NormalUnescapedProperty>a;b</NormalUnescapedProperty> | |
| <!-- プロパティ関数の結果として返された文字列はエスケープされる --> | |
| <AutomaticallyEscapedFunctionResultProperty>$([System.String]::Concat('a', ';', 'b'))</AutomaticallyEscapedFunctionResultProperty> | |
| <!-- 明示的に %NN という形式で書いた文字列もエスケープされた判定 --> | |
| <ExplicitlyEscapedProperty>a%3Bb</ExplicitlyEscapedProperty> | |
| <!-- Escape 関数でエスケープしてみる --> | |
| <ExplicitlyEscapedByFunctionProperty>$([MSBuild]::Escape($(NormalUnescapedProperty)))</ExplicitlyEscapedByFunctionProperty> | |
| <ExplicitlyEscapedByFunctionPropertyContainsPercentSign>false</ExplicitlyEscapedByFunctionPropertyContainsPercentSign> | |
| <ExplicitlyEscapedByFunctionPropertyContainsPercentSign Condition="$(ExplicitlyEscapedByFunctionProperty.Contains('%'))">true</ExplicitlyEscapedByFunctionPropertyContainsPercentSign> | |
| <ExplicitlyEscapedByFunctionPropertyContainsCharacterCode>false</ExplicitlyEscapedByFunctionPropertyContainsCharacterCode> | |
| <ExplicitlyEscapedByFunctionPropertyContainsCharacterCode Condition="$(ExplicitlyEscapedByFunctionProperty.Contains('3B'))">true</ExplicitlyEscapedByFunctionPropertyContainsCharacterCode> | |
| <!-- 見た目としてはどれも同じ。比較しても同じ。 --> | |
| <NormalUnescapedPropertyEqualsAutomaticallyEscapedFunctionResultProperty>false</NormalUnescapedPropertyEqualsAutomaticallyEscapedFunctionResultProperty> | |
| <NormalUnescapedPropertyEqualsAutomaticallyEscapedFunctionResultProperty Condition="'$(NormalUnescapedProperty)' == '$(AutomaticallyEscapedFunctionResultProperty)'">true</NormalUnescapedPropertyEqualsAutomaticallyEscapedFunctionResultProperty> | |
| <NormalUnescapedPropertyEqualsExplicitlyEscapedProperty>false</NormalUnescapedPropertyEqualsExplicitlyEscapedProperty> | |
| <NormalUnescapedPropertyEqualsExplicitlyEscapedProperty Condition="'$(NormalUnescapedProperty)' == '$(ExplicitlyEscapedProperty)'">true</NormalUnescapedPropertyEqualsExplicitlyEscapedProperty> | |
| <ExplicitlyEscapedByFunctionPropertyEqualsNormalUnescapedProperty>false</ExplicitlyEscapedByFunctionPropertyEqualsNormalUnescapedProperty> | |
| <ExplicitlyEscapedByFunctionPropertyEqualsNormalUnescapedProperty Condition="'$(ExplicitlyEscapedByFunctionProperty)' == '$(NormalUnescapedProperty)'">true</ExplicitlyEscapedByFunctionPropertyEqualsNormalUnescapedProperty> | |
| <!-- 値自体はエスケープ形式ではない(% や 3B は値として含んでいない) --> | |
| <ExplicitlyEscapedPropertyContainsPercentSign>false</ExplicitlyEscapedPropertyContainsPercentSign> | |
| <ExplicitlyEscapedPropertyContainsPercentSign Condition="$(ExplicitlyEscapedProperty.Contains('%'))">true</ExplicitlyEscapedPropertyContainsPercentSign> | |
| <ExplicitlyEscapedPropertyContainsCharacterCode>false</ExplicitlyEscapedPropertyContainsCharacterCode> | |
| <ExplicitlyEscapedPropertyContainsCharacterCode Condition="$(ExplicitlyEscapedProperty.Contains('3B'))">true</ExplicitlyEscapedPropertyContainsCharacterCode> | |
| <!-- 明示的にアンエスケープ --> | |
| <UnescapedAutomaticallyEscapedFunctionResultProperty>$([MSBuild]::Unescape($(AutomaticallyEscapedFunctionResultProperty)))</UnescapedAutomaticallyEscapedFunctionResultProperty> | |
| <UnescapedExplicitlyEscapedProperty>$([MSBuild]::Unescape($(ExplicitlyEscapedProperty)))</UnescapedExplicitlyEscapedProperty> | |
| <!-- 文字列連結によって組み立てる --> | |
| <CharA>a</CharA> | |
| <CharB>b</CharB> | |
| <CharSemicolon>;</CharSemicolon> | |
| <CharPercent>%</CharPercent> | |
| <CharExclamation>!</CharExclamation> | |
| <Char3B>3B</Char3B> | |
| <Concatenated1>$(CharA)$(CharSemicolon)$(CharB)</Concatenated1> | |
| <Concatenated2>$(CharA)$(CharPercent)$(Char3B)$(CharB)</Concatenated2> | |
| <Concatenated1EqualsConcatenated2>false</Concatenated1EqualsConcatenated2> | |
| <Concatenated1EqualsConcatenated2 Condition="'$(Concatenated1)' == '$(Concatenated2)'">true</Concatenated1EqualsConcatenated2> | |
| <Concatenated2ContainsPercentSign>false</Concatenated2ContainsPercentSign> | |
| <Concatenated2ContainsPercentSign Condition="$(Concatenated2.Contains('%'))">true</Concatenated2ContainsPercentSign> | |
| <Concatenated2ContainsCharacterCode>false</Concatenated2ContainsCharacterCode> | |
| <Concatenated2ContainsCharacterCode Condition="$(Concatenated2.Contains('3B'))">true</Concatenated2ContainsCharacterCode> | |
| <UnescapedConcatenated2>$([MSBuild]::Unescape($(Concatenated2)))</UnescapedConcatenated2> | |
| <Concatenated3>$(CharA)$(CharExclamation)$(Char3B)$(CharB)</Concatenated3> | |
| <ReplacedConcatenated3>$(Concatenated3.Replace('!', '%'))</ReplacedConcatenated3> | |
| <ReplacedConcatenated3EqualsConcatenated1>false</ReplacedConcatenated3EqualsConcatenated1> | |
| <ReplacedConcatenated3EqualsConcatenated1 Condition="'$(ReplacedConcatenated3)' == '$(Concatenated1)'">true</ReplacedConcatenated3EqualsConcatenated1> | |
| <UnescapedReplacedConcatenated3>$([MSBuild]::Unescape($(ReplacedConcatenated3)))</UnescapedReplacedConcatenated3> | |
| <UnescapedReplacedConcatenated3EqualsConcatenated1>false</UnescapedReplacedConcatenated3EqualsConcatenated1> | |
| <UnescapedReplacedConcatenated3EqualsConcatenated1 Condition="'$(UnescapedReplacedConcatenated3)' == '$(Concatenated1)'">true</UnescapedReplacedConcatenated3EqualsConcatenated1> | |
| <!-- %25 は % のエスケープ シーケンス --> | |
| <MultipleEscapedProperty>a%253Bb</MultipleEscapedProperty> | |
| <UnescapedMultipleEscapedProperty>$([MSBuild]::Unescape($(MultipleEscapedProperty)))</UnescapedMultipleEscapedProperty> | |
| <MultipleEscapedPropertyEqualsReplacedConcatenated3>false</MultipleEscapedPropertyEqualsReplacedConcatenated3> | |
| <MultipleEscapedPropertyEqualsReplacedConcatenated3 Condition="'$(MultipleEscapedProperty)' == '$(ReplacedConcatenated3)'">true</MultipleEscapedPropertyEqualsReplacedConcatenated3> | |
| <!-- エスケープの段数が 1 つずれているので等しくない --> | |
| <MultipleEscapedEqualsExplicitlyEscaped>false</MultipleEscapedEqualsExplicitlyEscaped> | |
| <MultipleEscapedEqualsExplicitlyEscaped Condition="'$(MultipleEscapedProperty)' == '$(ExplicitlyEscapedProperty)'">true</MultipleEscapedEqualsExplicitlyEscaped> | |
| </PropertyGroup> | |
| <ItemGroup> | |
| <NormalUnescapedPropertyAsItem Include="$(NormalUnescapedProperty)" /> | |
| <AutomaticallyEscapedFunctionResultPropertyAsItem Include="$(AutomaticallyEscapedFunctionResultProperty)" /> | |
| <ExplicitlyEscapedPropertyAsItem Include="$(ExplicitlyEscapedProperty)" /> | |
| <ExplicitlyEscapedByFunctionPropertyAsItem Include="$(ExplicitlyEscapedByFunctionProperty)" /> | |
| <UnescapedAutomaticallyEscapedFunctionResultPropertyAsItem Include="$(UnescapedAutomaticallyEscapedFunctionResultProperty)" /> | |
| <UnescapedExplicitlyEscapedPropertyAsItem Include="$(UnescapedExplicitlyEscapedProperty)" /> | |
| <Concatenated1AsItem Include="$(Concatenated1)" /> | |
| <Concatenated2AsItem Include="$(Concatenated2)" /> | |
| <UnescapedConcatenated2AsItem Include="$(UnescapedConcatenated2)" /> | |
| <ReplacedConcatenated3AsItem Include="$(ReplacedConcatenated3)" /> | |
| <UnescapedReplacedConcatenated3AsItem Include="$(UnescapedReplacedConcatenated3)" /> | |
| <MultipleEscapedPropertyAsItem Include="$(MultipleEscapedProperty)" /> | |
| <UnescapedMultipleEscapedPropertyAsItem Include="$(UnescapedMultipleEscapedProperty)" /> | |
| </ItemGroup> | |
| <Target Name="Show"> | |
| <Message Text="NormalUnescapedProperty: $(NormalUnescapedProperty)" /> | |
| <Message Text="AutomaticallyEscapedFunctionResultProperty: $(AutomaticallyEscapedFunctionResultProperty)" /> | |
| <Message Text="ExplicitlyEscapedProperty: $(ExplicitlyEscapedProperty)" /> | |
| <!-- 見た目としてはどれも同じ。比較しても同じ。 --> | |
| <Message Text="NormalUnescapedPropertyEqualsAutomaticallyEscapedFunctionResultProperty: $(NormalUnescapedPropertyEqualsAutomaticallyEscapedFunctionResultProperty)" /> | |
| <Message Text="NormalUnescapedPropertyEqualsExplicitlyEscapedProperty: $(NormalUnescapedPropertyEqualsExplicitlyEscapedProperty)" /> | |
| <!-- 値自体はエスケープ形式ではない --> | |
| <Message Text="ExplicitlyEscapedPropertyContainsPercentSign: $(ExplicitlyEscapedPropertyContainsPercentSign)" /> | |
| <Message Text="ExplicitlyEscapedPropertyContainsCharacterCode: $(ExplicitlyEscapedPropertyContainsCharacterCode)" /> | |
| <!-- Escape(a;b) しても見た目は a;b --> | |
| <Message Text="ExplicitlyEscapedByFunctionProperty: $(ExplicitlyEscapedByFunctionProperty)" /> | |
| <Message Text="ExplicitlyEscapedByFunctionPropertyEqualsNormalUnescapedProperty: $(ExplicitlyEscapedByFunctionPropertyEqualsNormalUnescapedProperty)" /> | |
| <!-- Escape 関数を通しても値は % や 3B を含まない --> | |
| <Message Text="ExplicitlyEscapedByFunctionPropertyContainsPercentSign: $(ExplicitlyEscapedByFunctionPropertyContainsPercentSign)" /> | |
| <Message Text="ExplicitlyEscapedByFunctionPropertyContainsCharacterCode: $(ExplicitlyEscapedByFunctionPropertyContainsCharacterCode)" /> | |
| <!-- 生の ; はアイテム区切りになる(Count = 2) --> | |
| <Message Text="NormalUnescapedPropertyAsItem.Count: @(NormalUnescapedPropertyAsItem->Count())" /> | |
| <!-- エスケープされた ; はアイテム区切りにならない(Count = 1) --> | |
| <Message Text="AutomaticallyEscapedFunctionResultPropertyAsItem.Count: @(AutomaticallyEscapedFunctionResultPropertyAsItem->Count())" /> | |
| <Message Text="ExplicitlyEscapedPropertyAsItem.Count: @(ExplicitlyEscapedPropertyAsItem->Count())" /> | |
| <!-- Unescape すればアイテム区切りとして使われる(Count = 2) --> | |
| <Message Text="UnescapedAutomaticallyEscapedFunctionResultProperty.Count: @(UnescapedAutomaticallyEscapedFunctionResultPropertyAsItem->Count())" /> | |
| <Message Text="UnescapedExplicitlyEscapedProperty.Count: @(UnescapedExplicitlyEscapedPropertyAsItem->Count())" /> | |
| <!-- Escape 関数でエスケープしたものはアイテム区切りにならない(Count = 1) --> | |
| <Message Text="ExplicitlyEscapedByFunctionPropertyAsItem.Count: @(ExplicitlyEscapedByFunctionPropertyAsItem->Count())" /> | |
| <!-- a と ; と b を文字列連結で組み立てたもの --> | |
| <Message Text="Concatenated1: $(Concatenated1)" /> | |
| <!-- a と % と 3b と b を文字列連結で組み立てたもの --> | |
| <Message Text="Concatenated2: $(Concatenated2)" /> | |
| <!-- % や 3b は含んでいない --> | |
| <Message Text="Concatenated2ContainsPercentSign: $(Concatenated2ContainsPercentSign)" /> | |
| <Message Text="Concatenated2ContainsCharacterCode: $(Concatenated2ContainsCharacterCode)" /> | |
| <!-- 値としては同じ --> | |
| <Message Text="Concatenated1EqualsConcatenated2: $(Concatenated1EqualsConcatenated2)" /> | |
| <!-- アイテム区切りとして使われる(Count = 2) --> | |
| <Message Text="Concatenated1AsItem.Count: @(Concatenated1AsItem->Count())" /> | |
| <!-- アイテム区切りとして使われない(Count = 1) --> | |
| <Message Text="Concatenated2AsItem.Count: @(Concatenated2AsItem->Count())" /> | |
| <!-- Unescape すればアイテム区切りとして使われる(Count = 2) --> | |
| <Message Text="UnescapedConcatenated2AsItem.Count: @(UnescapedConcatenated2AsItem->Count())" /> | |
| <Message Text="Concatenated3: $(Concatenated3)" /> | |
| <!-- この値は 'a%3Bb' --> | |
| <Message Text="ReplacedConcatenated3: $(ReplacedConcatenated3)" /> | |
| <!-- 'a%3Bb' != 'a;b' --> | |
| <Message Text="ReplacedConcatenated3EqualsConcatenated1: $(ReplacedConcatenated3EqualsConcatenated1)" /> | |
| <!-- '%3B' は当然アイテム区切りでもない(Count = 1)--> | |
| <Message Text="ReplacedConcatenated3AsItem.Count: @(ReplacedConcatenated3AsItem->Count())" /> | |
| <!-- Unescape すれば値として一致し、アイテム区切りとしても使われる(Count = 2) --> | |
| <Message Text="UnescapedReplacedConcatenated3: $(UnescapedReplacedConcatenated3)" /> | |
| <Message Text="UnescapedReplacedConcatenated3EqualsConcatenated1: $(UnescapedReplacedConcatenated3EqualsConcatenated1)" /> | |
| <Message Text="UnescapedReplacedConcatenated3AsItem.Count: @(UnescapedReplacedConcatenated3AsItem->Count())" /> | |
| <!-- この結果は↑と等しい --> | |
| <Message Text="MultipleEscapedProperty: $(MultipleEscapedProperty)" /> | |
| <Message Text="UnescapedMultipleEscapedProperty: $(UnescapedMultipleEscapedProperty)" /> | |
| <Message Text="MultipleEscapedPropertyAsItem.Count: @(MultipleEscapedPropertyAsItem->Count())" /> | |
| <Message Text="UnescapedMultipleEscapedPropertyAsItem.Count: @(UnescapedMultipleEscapedPropertyAsItem->Count())" /> | |
| <!-- 確かに等しい判定 --> | |
| <Message Text="MultipleEscapedPropertyEqualsReplacedConcatenated3: $(MultipleEscapedPropertyEqualsReplacedConcatenated3)" /> | |
| <!-- 'a%253Bb' != 'a%3Bb'(表示はどちらも a%3Bb / a;b だが内部のエスケープ段数が違う) --> | |
| <Message Text="MultipleEscapedEqualsExplicitlyEscaped: $(MultipleEscapedEqualsExplicitlyEscaped)" /> | |
| </Target> | |
| </Project> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment