【WSH】文字列置換(正規表現対応)ツール

  • 0
    Like
  • 0
    Comment
    More than 1 year has passed since last update.

    はじめに

    会社では運用チームに属していて、各県にある30近い工場の運用作業を行っています。
    システムの導入作業があると工場ごとに設定ファイルを変更して導入しており、現在は設定ファイルの編集は手作業で行っている。
    その為、「【WSH】設定ファイル(xml,ini)の編集ツール」(ReplaceConfig.vbs)を作成することで効率化することが出来た。しかし、製品名を変更したいという要望もあり、もう一工夫必要になった。
    ClickOnceの発行物を「MageUI.exe」を使うことで、製品名やバージョンやStart Locationなどの設定を変更することが出来ますが、製品名を変更しても「publish.htm」ファイルまでは更新されません。
    製品名が書かれている「.application」ファイルの中身はXMLファイルであるため、編集ツール(ReplaceConfig.vbs)を使うことで変更できたが、「publish.htm」ファイルはHTMLであり特定できるキーが無いため、別途、正規表現で置換するツールを作成することにしました。
    通常使われる文字コードや改行コードには対応しております。

    2016/11/16追記
    置換対象の値を取得できるといいなと思ったので、機能を追加しました。

    使用方法

    「ReplaceRegex.vbs」は引数を4つ指定できます。

    ReplaceRegex.vbs FileName Search Value [CharSet]
    
    • FileName
      置換対象ファイルを指定します。

    • Search
      検索文字列(正規表現対応)を指定します。例 /ab+c/gi
      正規表現を使う場合はJavascriptの正規表現の指定方法と同じく「/正規表現文字列/オプション」となります。オプションは2種類で「g」オプションはグローバルマッチ、「i」オプションは英大文字と小文字を無視です。
      正規表現を書く際に「<」や「>」や「|」などコマンドラインに使う文字や空白や含むや文字列は2重引用符で囲んでください。
      特殊文字「"」や「%」などを値として使う場合は、「#(16進数)」としてください。2重引用符の「"」は「#22」となります。

    • Value
      変更する値(正規表現対応)を指定します。例 \$1-\$2-\$3
      検索文字列が正規表現だった場合に後方参照など正規表現用の置換文字列を指定できます。
      また、「<」や「>」や「|」などコマンドラインに使う文字や空白や含むや文字列は2重引用符で囲んでください。
      特殊文字「"」や「%」などを値として使う場合は、「#(16進数)」としてください。2重引用符の「"」は「#22」となります。
      固定文字列「GETVALUE」にすると、置換ではなく置換対象の値を取得します。

    • CharSet
      BOM無しの場合の文字コードをセットします。不要な場合は指定する必要はありません。指定しない場合は「Shift-JIS」となります。
      ※ファイル内の5行目以内に「encoding」や「charset」があれば指定文字コードを優先します。

    使用例

    ClickOnceの「publish.htm」ファイルの製品名が「外注受払いシステム(イントラネットワーク使用)」、タイトルが「外注受払いシステム(イントラ側ネットワーク使用)」と「側」が付いているという違いがあります。この括弧内を「工程用」に変更します。

    正規表現使用
    ReplaceRegex publish.htm /イントラ.?ネットワーク使用/ 工程用
    
    正規表現使用(括弧を後方参照でセット)
    ReplaceRegex publish.htm /(\()(イントラ.?ネットワーク使用)(\))/ $1工程用$3
    

    「イントラ側ネットワーク使用」で統一されていた場合

    正規表現未使用
    ReplaceRegex publish.htm イントラ側ネットワーク使用 工程用
    

    image

    image

    値の取得

    最初に見つけた「イントラ側ネットワーク使用」を取得します。

    for /f "usebackq tokens=*" %%i IN (`call cscript //nologo ReplaceRegex.vbs publish.htm "/イントラ.?ネットワーク使用/" GETVALUE`) DO @set result=%%i
    
    echo %result%
    

    正規表現が使えるため括弧でグループ化するとカンマ区切りにして取得します。
    例 (,イントラ側ネットワーク使用,)
    ※バッチでは括弧はエスケープされてしまうため、2重引用符で囲む必要があります。

    for /f "usebackq tokens=*" %%i IN (`call cscript //nologo ReplaceRegex.vbs publish.htm "/(\()(イントラ.?ネットワーク使用)(\))/" GETVALUE`) DO @set result=%%i
    
    echo %result%
    

    ソースリスト

    ReplaceRegex.vbs
    '--------------------------------------
    '  引数1: 変更するファイル
    '  引数2: 検索文字列(正規表現)
    '  引数3: 変更文字列(正規表現)
    '  引数4: 文字コード(BOM以外)
    '--------------------------------------
    Option Explicit
    'On Error Resume Next
    
    Const ForReading = 1, ForWriting = 2, ForAppEnding = 8
    Const adTypeBinary = 1, adTypeText = 2
    Const adReadAll = -1, adReadLine = -2
    
    Const GET_CONFIG = "GETVALUE"
    
    Dim objArgs, fso
    Dim pathName, searchPattern, valuePattern, charSet, strLine
    
    'コマンドライン引数取得
    Set objArgs = WScript.Arguments
    
    '対象パス名取得
    If objArgs.Count >= 1 Then
        pathName = objArgs(0)
        Set fso = CreateObject("Scripting.FileSystemObject")
        If fso.FileExists(pathName) = False Then
            WScript.Echo pathName & " ファイルが存在しません"
            WScript.Quit(1)
        End If
        Set fso = Nothing
    Else
        WScript.Echo "対象パス名が見つかりません"
        WScript.Quit(1)
    End If
    
    '検索文字列(正規表現)取得
    If objArgs.Count >= 2 Then
        searchPattern = objArgs(1)
    Else
        WScript.Echo "検索文字列が見つかりません"
        WScript.Quit(2)
    End If
    
    '変更文字列(正規表現)取得
    If objArgs.count >= 3 then
        valuePattern = objArgs(2)
    Else
        WScript.Echo "変更文字列が見つかりません"
        WScript.Quit(3)
    End If
    
    '文字コード(BOM以外)
    If objArgs.count >= 4 then
        charSet = UCase(objArgs(3))
    End If
    
    'ファイル情報を取得する
    Dim param, result
    Set param = CreateObject("Scripting.Dictionary")
    Call GetInfo(pathName, charSet)
    
    '正規表現対応の置換処理
    result = Main(pathName, searchPattern, valuePattern)
    Set param = Nothing
    
    WScript.Quit(result)
    
    '------------------------------------------------------------------------
    '  正規表現対応の置換処理
    '  pathName:        対象パス名
    '  searchPattern:   検索文字列(正規表現)
    '  valuePattern:    変更文字列(正規表現)
    '------------------------------------------------------------------------
    Function Main(pathName, searchPattern, valuePattern)
    
        Dim reg, matches, match, stream_r, stream_pre, stream_w
        Dim bin, newtxt, line, lineSave, i, idx, count, regPattern
        Dim regexFlag, exitFlag, lfType, allLine, aryLine, result
    
        Main = 0
    
        Set stream_r = CreateObject("ADODB.Stream")
        Set stream_pre = CreateObject("ADODB.Stream")
        Set stream_w = CreateObject("ADODB.Stream")
        Set reg = CreateObject("VBScript.RegExp")
        reg.Global = True
        reg.IgnoreCase = True
    
        '現行の文字コードで読み込む
        stream_r.Open
        stream_r.CharSet = param("CharSet")
        stream_r.LoadFromFile pathName
        allLine = stream_r.ReadText(adReadAll)
        stream_r.Close
    
        '改行コード
        lfType = ""
        Select Case param("LfType")
            Case "CrLf": lfType = vbCrLf
            Case "Lf": lfType = vbLf
            Case "Cr": lfType = vbCr
        End Select
    
        '改行コードで分割
        aryLine = Split(allLine, lfType)
    
        '検索文字列の#(16進数)の文字変換
        searchPattern = ReplaceChr(searchPattern)
    
        '変更文字列の#(16進数)の文字変換
        valuePattern = ReplaceChr(valuePattern)
    
        '検索文字列が正規表現かを確認
        regexFlag = False
        reg.Pattern =  "\/(.*)\/([gi]*)$"
        reg.Global = False
        reg.IgnoreCase = False
        Set matches = reg.Execute(searchPattern)
        regexFlag = (matches.Count > 0)
        For Each match In matches
            regPattern = match.SubMatches(0)
    
            Select case match.SubMatches(1)
                Case "g"
                    reg.Global = True
                Case "i"
                    reg.IgnoreCase = True
                Case "gi","ig"
                    reg.Global = True
                    reg.IgnoreCase = True
            End Select
        Next
        Set matches = Nothing
    
        i = 0
        count = 0
        exitFlag = False
        For idx = 0 To UBound(aryLine)
            line = aryLine(idx)
            If regexFlag = True Then
                '正規表現による置換
                reg.Pattern = regPattern
                If reg.Test(line) Then
                    '置換
                    result = GetValue(reg, valuePattern, line)
                    line = reg.Replace(line, valuePattern)
                    exitFlag = True
                End If
            Else
                '通常の置換
                result = GetValue(reg, valuePattern, line)
                lineSave = line
                line = Replace(line, searchPattern, valuePattern)
                If line <> lineSave Then exitFlag = True
            End If
    
            '読み込んだ行を再セット
            aryLine(idx) = line
        Next
    
        If exitFlag = True Then
            If UCase(valuePattern) = GET_CONFIG Then
                '値を返す
                WScript.Echo result
            Else
                '改行を付加
                newtxt = Join(aryLine, lfType)
    
                'ファイル保存
                If param("CharSet") = "UTF-8" And param("IsBOM") = False Then
                    'BOM無し
                    stream_pre.Type = adTypeText
                    stream_pre.CharSet = param("CharSet")
                    stream_pre.Open
                    stream_pre.WriteText newtxt
    
                    stream_pre.Position = 0
                    stream_pre.Type = adTypeBinary
                    stream_pre.Position = 3
                    bin = stream_pre.Read()
                    stream_pre.Close()
    
                    stream_w.Type = adTypeBinary
                    stream_w.Open()
                    stream_w.Write(bin)
                    stream_w.SaveToFile pathName, ForWriting
                    stream_w.Close()
                Else
                    stream_w.Open()
                    stream_w.Charset = param("CharSet")
                    stream_w.WriteText newtxt
                    stream_w.SaveToFile pathName, ForWriting
                    stream_w.Close()
                End If
            End If
        Else
            WScript.Echo "検索文字列が見つかりませんでした。"
            Main = 9
        End If
    
        Set reg = Nothing
    
    End Function
    
    '------------------------------------------------------------------------
    '#(16進数)の文字変換
    '------------------------------------------------------------------------
    Function ReplaceChr(target)
        Dim reg, matches, match, hexvalue
    
        Set reg = CreateObject("VBScript.RegExp")
        reg.Global = True
        reg.IgnoreCase = True
    
        reg.Pattern =  "#([0-9A-Fa-f]{2})"
        Set matches = reg.Execute(target)
        For Each match In matches
            hexvalue = match.SubMatches(0)
            target = Replace(target, "#" & hexvalue, Chr(CInt("&H" & hexvalue)))
        Next
        Set matches = Nothing
    
        ReplaceChr = target
    
        Set reg = Nothing
    End Function
    
    '------------------------------------------------------------------------
    'ファイルの文字コード等の情報を取得する
    '------------------------------------------------------------------------
    Function GetInfo(pathName, charSet)
    
        Dim stream, reg, match, matches, bytCode, allLine, line, endLine, lfType, aryLine
        Dim i, charcode
    
        Const FindMaxLine = 5               'BOM無し時の文字コード探索行
        GetInfo = False
    
        '初期値セット
        param.Add "CharSet", "SJIS"         '文字コード
        param.Add "IsBOM", False            'BOM有無
        param.Add "LfType", "CrLf"          '改行コード
        param.Add "IsEndLf", False          '末尾改行有無
    
        'ファイルをオープン
        Set stream = CreateObject("ADODB.Stream")
    
        'BOMチェック用に先頭行を読み込む
        stream.Type = adTypeBinary
        stream.Open
        stream.LoadFromFile pathName
        bytCode = stream.Read
        stream.Close
        Call CheckBOM(bytCode)
    
        '文字コードを自動判定で読み込み
        stream.Type = adTypeText
        stream.CharSet = "_autodetect_all"
        If charSet <> "" Then stream.CharSet = charSet
        stream.Open
        stream.LoadFromFile pathName
        allLine = stream.ReadText(adReadAll)
        stream.Close
    
        '改行コード判定(100文字で判断)
        line = Left(allLine, 100)
        If Instr(line, vbCrLf) <> 0 Then
            lfType = "CrLf"
        ElseIf Instr(line, vbLf) <> 0 Then
            lfType = "Lf"
        ElseIf Instr(line, vbCr) <> 0 Then
            lfType = "Cr"
        End If
        param("LfType") = lfType
    
        '改行コードで分割
        Select Case param("LfType")
            Case "CrLf": lfType = vbCrLf
            Case "Lf": lfType = vbLf
            Case "Cr": lfType = vbCr
        End Select
        aryLine = Split(allLine, lfType)
    
        'BOM無しなら、encodingとcharsetの文字コード参照
        If param("IsBOM") = False Then
            Set reg = CreateObject("VBScript.RegExp")
            'encodingまたはcharsetの値を文字コードにセット
            reg.Pattern = "(?:encoding|charset).*?=(?:['""](.*?)['""]|(.*))"
            reg.IgnoreCase = True
            For i = 0 To FindMaxLine - 1
                Set matches = reg.Execute(aryLine(i))
                For Each match In matches
                    charcode = UCase(match.SubMatches(0) & match.SubMatches(1))
                    param("CharSet") = charcode
                Next
                If charcode > "" Or i >= UBound(aryLine) Then Exit For
            Next
            Set matches = Nothing
            Set reg = Nothing
        End If
    
        '最終行の改行コード有無
        If aryLine(UBound(aryLine)) = "" Then
            param("IsEndLf") = True
        End If
    
        GetInfo = True
    
    End Function
    
    '------------------------------------------------------------------------
    'BOMチェックの情報を取得する
    '------------------------------------------------------------------------
    Function CheckBOM(str)
        Dim bytes(2), result
    
        bytes(0) = AscB(MidB(str, 1, 1))
        bytes(1) = AscB(MidB(str, 2, 1))
        bytes(2) = AscB(MidB(str, 3, 1))
    
        If LenB(str) > 1 Then
            If bytes(0) >= &HFE And bytes(1) >= &HFE Then
                result = "unicode"
                If bytes(0) = &HFF And bytes(1) = &HFE Then
                    result = "unicodeFFFE"
                End If
            Else If bytes(0) = &HEF And bytes(1) = &HBB And bytes(2) = &HBF Then
                    result = "UTF-8"
                End If
            End If
        End If
    
        If result <> "" Then
            param("CharSet") = result
            param("IsBOM") = True
        End If
    
        CheckBOM = result
    
    End Function
    
    '------------------------------------------------------------------------
    '現在の設定値を取得する
    '------------------------------------------------------------------------
    Function GetValue(reg, valueString, target)
        Dim matches, match, comma, i
    
        GetValue = ""
        If UCase(valueString) = GET_CONFIG Then
            Set matches = reg.Execute(target)
            For Each match In matches
                If match.SubMatches.Count > 0 Then
                    comma = ""
                    For i=0 To match.SubMatches.Count - 1
                        GetValue = GetValue & comma & match.SubMatches(i)
                        comma = ","
                    Next
                Else
                    GetValue = match.Value
                    Exit For
                End If
            Next
        End If
    
    End Function
    

    ライセンスっぽいこと

    • コード改変や配布は自由です。
    • このツールによる義務/責任を何ら負いません。

    最後に

    「コマンドライン 正規表現 置換」で検索すると、「mfind」や「sakuraエディタのコマンドラインオプション」ぐらいしか見つからないのであまり需要はないかな。
    プログラムは不具合があれば随時修正していきます。

    このツールがお役に立てれば幸いです。