はじめに
Unix系のdiffみたいな差分比較を行いたい...
DFやWinmergeから卒業したい...
文字列の差異比較が難しいPowershell
文字列差異比較なんて単純に思えるし、CLIならばそれができるコマンドが当たり前に整備されていそうなもの
差異を比較するコマンドレット..
あります。その名はCompare-Object
#testDiff1
cat .\testDiff1.txt
Hello!
This is testDiff1
#testDiff2
cat .\testDiff2.txt
Hello!
This is testDiff2
Compare-Object .\testDiff1.txt .\testDiff2.txt
InputObject SideIndicator
----------- -------------
.\testDiff2.txt =>
.\testDiff1.txt <=
何か思ってたんと違う^^
よくみるとファイル名の差分を比較しているではないか...
まぁよく考えてみるとCompare-Object
だから「ファイル内のテキスト情報を読み取って比較する」なんてニュアンスはどこにもないですね。
そうこのコマンドレットは2つのオブジェクトを差分比較するコマンドレットなのでした(紛らわしいー)
テキストを差分比較はどうすればいいのか?
オブジェクト同士を比較するのであれば、それ用のオブジェクト同士を比較すればいいのではないだろうか?
つまり
- ファイルを読み取りその内容を変数に格納(Stringオブジェクトの作成)
- ファイル1・ファイル2の各Stringオブジェクトを作成し、それを比較(Compare-Object)する。
これでいけるかも...
Compare-Object $txt1 $txt2
InputObject SideIndicator
----------- -------------
This is testDiff2 =>
This is testDiff1 <=
なんかそれっぽくなった
でも差異が存在する行位置の表示が無いからちょっとわかりにくいね...
文字列比較用の関数を作成する
上記の処理を比較毎にいちいち実施するのも面倒なので関数化してしまいましょう。
おまけで行数の情報もいれて扱いしやすくしましょう。
ソースコード
function Compare-Text() {
#パラメータ定義
Param(
[switch]$guiDialog,
[Parameter(Mandatory=$false)]
[ValidateSet("Default","UTF8")]
$Encoding = "Default",
[Parameter(Position=0)]
[string]$referencePath = "",
[Parameter(Position=1)]
[string]$differencePath = ""
)
#ファイル選択
if($guiDialog){
$getFile = "Select-File -guidialog"
}else{
$getFile = "Select-File"
}
if($referencePath -eq ""){
Write-Host "比較元ファイルを選択してください"
$referencePath = Invoke-Expression $getFile
}
$referenceText = Get-Content -Encoding $Encoding $referencePath
if($differencePath -eq ""){
Write-Host "差分ファイルを選択してください"
$differencePath = Invoke-Expression $getFile
}
$differenceText = Get-Content -Encoding $Encoding $differencePath
#差分比較
$tmp=Compare-Object -ReferenceObject $referenceText -DifferenceObject $differenceText
$diffObjects = $tmp | ForEach-Object {
[PSCustomObject]@{
Line = $_.inputobject.Readcount
Text = $_.inputobject
Indicator = $_.sideindicator
}
}
$diffObjects = $diffObjects | Sort-Object -Property Line
return $diffObjects
}
書式
Compare-Text [-guiDialog] [-Encoding Default | UTF8 ] ファイル1 ファイル2
出力サンプル
Line Text Indicator
---- ---- ---------
2 This is testDiff1 <=
2 This is testDiff2 =>
3 こんにちは <=
3 こんばんは =>
オプション
オプション | 説明 |
---|---|
-guiDialog | GUIのダイアログを使用してファイルを選択します。 |
-Encoding | ファイルの文字コードを指定します。 DefaultはSJISです。 |
-referencePath | 比較元ファイルのパスを指定します。 Indicatorで"<="で示されるファイルです。 |
$differencePath | 比較ファイルのパスを指定します。 Indicatorで"=>"で示されるファイルです。 |
返値
以下のプロパティを含むカスタムオブジェクトが返されます。
プロパティ | 説明 |
---|---|
Line | 差異発生個所の行位置 |
Text | 差異内容 |
Indicator | <=:ファイル1にだけ存在 =>:ファイル2にだけ存在 |
前提・注意点
比較対象のパスを未指定時に対話形式でファイルを選択するモードになりますが、
これはSelect-File
という別の自作関数を実行することで実現しています。
この機能を使用する場合は以下のページに貼られているモジュールのインポートが必要です。
文字コードはSJISとUTF8に対応しています。
オプション-Encoding
で指定します。
尚、デフォルトはPowershellの標準文字コードであるSJISに設定されています。
補足
関数内の処理で行番号を基準に昇順でソートしています。
ただし、関数の処理結果をカスタムオブジェクトとして返すようにしているため関数の出力をSort-Object
などでパイプして任意のプロパティでソートかけることで、Text
やIndicator
の値で並び替えたり降順で出力したりといった対応も可能です。
制作時備忘
位置指定パラメータを特定の値で固定する
4つのオプションの内、1つがSwitch
型、残り3つがString
型のパラメータとなるため、
そのままだと位置指定パラメータが3つ受け入れ可能となってしまう。
オプションは以下の順で定義されている。
- 文字コード(-Encoding)
- ファイル1(-referencePath)
- ファイル2(-differencePath)
即ち、以下のように関数を実行すると第1引数が文字コードを指定したものと判別されてしまう。
Compare-Text ファイル1 ファイル2
Compare-Text : パラメーター 'Encoding の引数を確認できません。引数 "XXXXX.txt" は、ValidateSet 属性で指定されたセット "Default,UTF8" に属していません。このセットの引数を指定して、コマンドを再度実行してください。
発生場所 行:1 文字:14
+ Compare-Text XXXXX.txt XXXXX.txt
+ ~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidData: (:) [Compare-Text]、ParameterBindingValidationException
+ FullyQualifiedErrorId : ParameterArgumentValidationError,Compare-Text
単純に定義する順番を変えて文字コードのオプションを最後に定義することで回避することも可能だが、
並び順がなんか腑に落ちないなと感じた。
そこでファイル1とファイル2が固定の位置パラメータを持たすことができればいいのではないか?と考えた。
特定のオプションに対して任意の位置指定パラメータを割り当てるにはParameter
属性に`Position=インデックス``を明示することで実現可能だった。
[Parameter(Position=0)]
[string]$referencePath = "",
[Parameter(Position=1)]
[string]$differencePath = ""
これにより第1引数は必ず「ファイル1」、第2引数は必ず「ファイル2」を指定したものと認識させることが可能となった。
参考