Powershell和Regex:如何替换命名部分中的INI名称,值对?

时间:2015-04-17 00:37:45

标签: regex powershell file-io

我正在使用PowerShell v.2.0中的INI文件

我想在具体部分中更改现有的 name = value 对。例如:我想为 [Sky] 部分设置 color = Blue ,而不更改 [Home] 部分内的相同名称,值对。

我有正则表达式来获得我想改变的完整部分('天空'):

[[ \t]*Sky[ \t]*\](?:\r?\n(?:[^[\r\n].*)?)*

我还有另一个有效的表达式,但前提是Color是该部分的名字,值对。

\[[ \t]*Sky[ \t]*\][.\s]*Color[ \t]*=[ \t]*(.*) 

这是示例ini文件:

[Sky]
Size=Big
Color=white

[Home]
Color=Black

PS:这是我现在用来替换ini文件中name = value的所有实例的代码,我想用新表达式更新以仅替换在给定的部分

$Name=Color
$Value=Blue

$regmatch= $("^($Name\s*=\s*)(.*)")
$regreplace=$('${1}'+$Value)

if ((Get-Content $Path) -match $regmatch)
{
    (Get-Content -Path $Path) | ForEach-Object { $_ -replace $regmatch,$regreplace } | Set-Content $Path
}

修改:

@TheMadTechnician 解决方案非常完美:) 这里是Gist中的代码:ReplaceIniValue_Sample2.ps1

@Matt 解决方案是另一种方法:
以下是完整的代码:ReplaceIniValue_Sample1.ps1

我正在寻找基于正则表达式的解决方案,但 @mark z 提案运行良好,是Windows PowerShell API调用的一个很好的示例。以下是完整的代码,只需稍作调整即可:ReplaceIniValue_Sample3.ps1

4 个答案:

答案 0 :(得分:3)

也许另一种解决方案根本就不使用正则表达式并调用Windows API函数WritePrivateProfileString。这是为了编辑ini文件。但是从PowerShell执行PInvoke有点棘手,你必须使用Add-Type编译一些C#:

$source = @"

    using System.IO;
    using System.Runtime.InteropServices;
    using System.Text;

    public static class IniFile
    {
        [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
        [return: MarshalAs(UnmanagedType.Bool)]
        private static extern bool WritePrivateProfileString(string lpAppName,
           string lpKeyName, string lpString, string lpFileName);

        [DllImport("kernel32.dll", CharSet = CharSet.Unicode)]
        static extern uint GetPrivateProfileString(
           string lpAppName,
           string lpKeyName,
           string lpDefault,
           StringBuilder lpReturnedString,
           uint nSize,
           string lpFileName);

        public static void WriteValue(string filePath, string section, string key, string value)
        {
            string fullPath = Path.GetFullPath(filePath);
            bool result = WritePrivateProfileString(section, key, value, fullPath);
        }

        public static string GetValue(string filePath, string section, string key, string defaultValue)
        {
            string fullPath = Path.GetFullPath(filePath);
            var sb = new StringBuilder(500);
            GetPrivateProfileString(section, key, defaultValue, sb, (uint)sb.Capacity, fullPath);
            return sb.ToString();
        }
    }
"@

Add-Type -TypeDefinition $source

function Set-IniValue {

   param (
       [string]$path,
       [string]$section,
       [string]$key,
       [string]$value
   )

   $fullPath = [System.IO.Path]::GetFullPath($(Join-Path $pwd $path))
   [IniFile]::WriteValue($fullPath, $section, $key, $value)
}

然而,一旦你这样做,编辑ini键值对很容易:

Set-IniValue -Path "test.ini" -Section Sky -Key Color -Value Blue

关于用法的一些注意事项。设置空值将删除该键。设置空键将删除整个部分。

注意,有一个相应的GetPrivateProfileString用于从ini文件中读取值。我已经为此编写了C#部分,我将把它留给读者来练习PowerShell函数。

答案 1 :(得分:2)

以下是我要做的事情:将整个文件作为单个字符串读取,然后更改您的RegEx匹配以使其成为多行匹配,然后对[Sky]进行反向查找捕捉除了' ['直到您想要更改的选项,捕获第二个捕获组中等号另一侧的所有内容,并根据需要进行替换。

$Path="C:\Temp\prueba.ini"

@"
[Sky]
Size=Big
Color=White

[Home]
Color=Black
"@ | Set-content $Path

$Section="Sky"
$Name="Color"
$Value="Blue"

(Get-Content $Path) -join "`n" -replace "(?m)(?<=\[$Section\])([^\[]*$Name=)(.+?)$","`$1$Value" | Set-Content $Path

修改:几乎忘记了强制性的RegEx101链接来解释我的RegEx:https://regex101.com/r/uC0cC3/1

Edit2 :为了兼容v2,将-Raw更改为-ReadCount 0

编辑3:好的,我显然错误地记得。我认为-ReadCount 0-Raw的工作方式相同,但事实并非如此。更新了代码以解决此问题。现在它将字符串数组与新行字符连接,基本上将其转换为here-string,现在-replace按预期工作。

答案 2 :(得分:1)

好的,我们来看看吧。

@"

#This line have a white line after and before

[Sky]
Size=Big

Color=White

[Home]
Color=Black
#Last line
"@ | Set-content c:\temp\test.ini

只需使用该代码即可创建文件。现在让我们做一些改变。

$edits = (Get-Content c:\temp\test.ini) -join "`r`n" -split '\s(?=\[.+?\])' | ForEach-Object{
    If($_ -match '\[sky\]'){
        $_ -replace '(color=)\w+', '$1Blue'
    } Else {
        $_
    }
}

$edits | Set-Content c:\temp\test.ini

使用Get-Content读取ini文件,然后将-join读入一个大字符串。我们使用-split将文件分解为[parts]。我们循环每个部分寻找包含[sky]的部分。当我们发现该部分正在寻找replace之后带有蓝色的“颜色”字样。

这种方法有一些警告,但如果文件足够简单,它应该可以工作。具有[word]格式的代码中的值显然会导致解析不按预期工作,但结果应该仍然相同。

测试中的间距问题

直到JuanAntonioTubío让我意识到随后的代码传递创建了额外的空白,我才意识到这一点。那就是我如何分解文件进行解析。我通过在输出文件之前将数组连接成一个字符串来修复它。为了使后续测试更容易执行,我也做了一个功能。

function Set-INIKey{
    param(
        [string]$path,
        [string]$section,
        [string]$key,
        [string]$value
    )

    $edits = (Get-Content $path) -join "`r`n" -split '\s(?=\[.+?\])' | ForEach-Object{
        If($_ -match "\[$section\]"){
            $_ -replace "($key=)\w+", "`$1$value"
        } Else {
            $_
        }
    }

    -join $edits | Set-Content $path
}

因此,当我在几次测试中使用它时,额外的空格不再存在。

Set-INIKey "c:\temp\test.ini" "Sky" "Color" "Blue"

答案 3 :(得分:0)

 (Get-Content $path -raw) -join "`n" -replace "(?mi)(?<=\[$Section\])([^\[]*$Name=)(.*?)(\r?)$","`$1$Value`$3" | Set-Content $path -NoNewline

TheMadTechnician的回答稍有改进,可以保留回车符(如果存在)(并且不添加其他换行符)。