powershell iniファイルをテスト用などでしょっちゅう書き換えるときのツール作ってみた
powershellの練習がてら作成。
コメントや空行などを維持したままで更新してくれる。
<# .SYNOPSIS iniファイルへの変更指示をコマンドラインから受け付けて処理する .example .\iniUtil セクション名1.パラメータ1=値1 セクション名2.パラメータ2=値2 スペース区切りなので=の両端にはスペースを入れない 値にダブルクオーテーションはあっても無くても同じ .outputs 現在のiniファイル名と同じで作成する 元ファイルは_bakタイムスタンプを付けてバックアップする #> using namespace System.Collections.Generic using namespace System.IO $inifile = [INIFile]::new(".\AAAA.ini") #iniファイル名が固定の場合。パラメータにしても良いだろう Write-Host "input file $($inifile.name)" $section = [Section]::new("") $lineno = 0 Get-Content $inifile.name | ForEach-Object { if($_ -match "^\[.+\]") { $inifile.sections.add($section.name, $section) $section = [Section]::new($_) Write-Host "input section $_" return #foreach-objectではcontinueではなくreturnで次ループへ移る } else { $line = [INILine]::new($_) } if($line.type -eq [LineType]::param){ if($section.lines.containskey($line.paramname)){ "警告 --------- 重複パラメータ 無視しました $($line.paramname)"} else{ $section.lines.add($line.paramname, $line) } } else { $section.lines.add($lineno, $line) } $lineno++ } $inifile.sections.add($section.name, $section) #最後は終わりの合図がないので強制 $changecount = 0 #変更箇所を反映 $args.ForEach({ $match = [regex]::Match($_, "^(.+)\.(.+)=(.+)$") #sec.key=val if(!$match.Success) { Write-Host " ---- invalid param $_" continue #foreachメソッドの場合は普通にこれで良い } $arg = [Arg]::new($match.Groups[1].value, $match.Groups[2].value, $match.Groups[3].value) $sec = $inifile.sections[$arg.sec] if($sec -eq $null){ $sec = [Section]::new("[$($arg.sec)]") Write-Host " ---- add Section $($arg.sec)" $inifile.sections.add($arg.sec, $sec) } $params = $sec.lines $arg.val = '"' + $arg.val + '"' if($params.containskey($arg.key)){ Write-Host " ---- change $($arg.sec).$($arg.key) = $($arg.val)" $params[$arg.key].value = $arg.val } else { Write-Host " ---- add $($arg.sec).$($arg.key) = $($arg.val)" $params.add($arg.key, [INILine]::new("$($arg.key) = $($arg.val)")) $params.add("", [INILine]::new("")) #空行も追加 } $changecount++ }) if($changecount -gt 0) { #changecountがあるときのみ書き換え $bakfile = $inifile.name + "_bak" + [DateTime]::now.ToString("yyyyMMddHHmmss") Copy-Item $inifile.name ($bakfile) #バックアップ Write-Host " ---- backup $bakfile" $inifile.save($inifile.name) Write-Host " ---- save $($inifile.name)" } enum LineType { param comment empty } class INILine { [LineType] $type [string] $paramname [string] $value INILine([String] $line){ #class内ではWrite-Outputは出力されない #Write-Host "new line constructor" if($line -match "^[ \t]*;") { $this.type = [LineType]::comment $this.value = $line } elseif($line -match "^[ \t]*$"){ $this.type = [LineType]::empty } else { $match = [regex]::Matches($line, "^[ \t]*(.+)=(.+)") if($match.success) { $this.type = [LineType]::param $this.paramname = $match.Groups[1].value.trim() $this.value = $match.Groups[2].value.trim() } else { Write-Host "ignore line $line" $this.type = [LineType]::empty } } } out([StreamWriter] $w){ switch ($this.type) { ([LineType]::comment) { $w.WriteLine("$($this.value)") } ([LineType]::empty) { $w.WriteLine("") } ([LineType]::param) { $w.WriteLine("$($this.paramname) = $($this.value)") } } } } class Section { [string] $name #nameが無ければ名前のないSection(ファイル先頭など) [Dictionary[[string],[INILine]]] $lines = [Dictionary[[string],[INILine]]]::new() Section([string] $s){ $this.name = [regex]::Match($s, "\[(.+)\]").groups[1].Value } out([StreamWriter] $w){ if($this.name -ne "") { $w.WriteLine("[$($this.name)]") } foreach($k in $this.lines.keys){ $this.lines[$k].out($w) } } } class INIFile { [string] $name; [Dictionary[[string],[Section]]] $sections = [Dictionary[[string],[Section]]]::new() INIFile ([string] $n){ $this.name = $n } save([string] $filename){ $outfile = [StreamWriter]::new((Get-Location).ToString() + "\" + $filename, $false, [System.Text.Encoding]::GetEncoding("shift-jis")) foreach($k in $this.sections.keys) { $this.sections[$k].out($outfile) } $outfile.Close() } } class Arg { [string] $sec [string] $key [string] $val Arg ($s, $k, $v){ $this.sec = $s $this.key = $k $this.val = $v } }