1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

【PowerShell】Unixのdiffライクな文字列差異比較をPowerShellで行う

Last updated at Posted at 2023-11-11

はじめに

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
Compare-Object .\testDiff1.txt .\testDiff2.txt

InputObject     SideIndicator
-----------     -------------
.\testDiff2.txt =>
.\testDiff1.txt <=

何か思ってたんと違う^^
よくみるとファイル名の差分を比較しているではないか...

まぁよく考えてみるとCompare-Objectだから「ファイル内のテキスト情報を読み取って比較する」なんてニュアンスはどこにもないですね。

そうこのコマンドレットは2つのオブジェクトを差分比較するコマンドレットなのでした(紛らわしいー)

テキストを差分比較はどうすればいいのか?

オブジェクト同士を比較するのであれば、それ用のオブジェクト同士を比較すればいいのではないだろうか?
つまり

  1. ファイルを読み取りその内容を変数に格納(Stringオブジェクトの作成)
  2. ファイル1・ファイル2の各Stringオブジェクトを作成し、それを比較(Compare-Object)する。

これでいけるかも...

Stringオブジェクト同士で比較
Compare-Object $txt1 $txt2

InputObject       SideIndicator
-----------       -------------
This is testDiff2 =>
This is testDiff1 <=

なんかそれっぽくなった

でも差異が存在する行位置の表示が無いからちょっとわかりにくいね...

文字列比較用の関数を作成する

上記の処理を比較毎にいちいち実施するのも面倒なので関数化してしまいましょう。
おまけで行数の情報もいれて扱いしやすくしましょう。

ソースコード

Compare-Text
    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などでパイプして任意のプロパティでソートかけることで、TextIndicatorの値で並び替えたり降順で出力したりといった対応も可能です。

制作時備忘

位置指定パラメータを特定の値で固定する

4つのオプションの内、1つがSwitch型、残り3つがString型のパラメータとなるため、
そのままだと位置指定パラメータが3つ受け入れ可能となってしまう。
オプションは以下の順で定義されている。

  1. 文字コード(-Encoding)
  2. ファイル1(-referencePath)
  3. ファイル2(-differencePath)

即ち、以下のように関数を実行すると第1引数が文字コードを指定したものと判別されてしまう。

第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」を指定したものと認識させることが可能となった。

参考

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?