Powershell和Regex:如何替换命名节中的INI名称、值对?

Powershell和Regex:如何替换命名节中的INI名称、值对?,regex,powershell,file-io,Regex,Powershell,File Io,我正在PowerShell v.2.0中使用INI文件 我想更改混凝土截面中现有的name=value对。举例:我想为[Sky]部分设置color=Blue,而不更改[Home]部分中的相同名称、值对 我有正则表达式来获取我想要更改的完整部分(“天空”): 我还有另一个表达式可以工作,但前提是颜色是节的名字和值对 \[[ \t]*Sky[ \t]*\][.\s]*Color[ \t]*=[ \t]*(.*) 这是示例ini文件: [Sky] Size=Big Color=white [Ho

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

我想更改混凝土截面中现有的name=value对。举例:我想为[Sky]部分设置color=Blue,而不更改[Home]部分中的相同名称、值对

我有正则表达式来获取我想要更改的完整部分(“天空”):

我还有另一个表达式可以工作,但前提是颜色是节的名字和值对

\[[ \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解决方案非常完美:) 以下是要点中的代码:

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


我正在寻找一个基于正则表达式的解决方案,但是,@mark z建议非常有效,是Windows PowerShell API调用的一个很好的例子。下面是经过一些小调整的完整代码:

好的,让我们来看看这个

@"

#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
使用
获取内容
读入ini文件,然后将其加入一个大字符串中。我们使用
-split
将文件分解为
[部分]
。我们循环每个部分,寻找包含
[sky]
的部分。当我们找到该部分时,只需
后面的单词“color”替换为蓝色

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

测试中的间距问题

直到胡安·安东尼奥·图比奥(Juan Antonio Tubí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"

我要做的是:将整个文件作为单个字符串读取,然后更改正则表达式匹配,使其成为多行匹配,然后对[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
$Path=“C:\Temp\prueba.ini”
@"
[天空]
大小=大
颜色=白色
[主页]
颜色=黑色
“@|设置内容$Path
$Section=“天空”
$Name=“Color”
$Value=“蓝色”

(获取内容$Path)-加入“`n”-替换”(?m)(?也许另一种解决方案根本不使用正则表达式,而是调用Windows API函数
WritePrivateProfileString
。这是为了编辑ini文件而设计的。但是,从PowerShell执行PInvoke有点棘手,您必须编译一些带有Add类型的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
有关用法的几点说明。设置null值将删除该键。设置null键将删除整个部分

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

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

(Get Content$path-raw)-join“`n”-replace”(?mi)(?谢谢@TheMadTechnician,我已经测试了你的和RegEx101的工作,但没有测试poweshell:(完整代码的要点:这是我的错,我错误地记得-ReadCount 0是如何工作的,并且认为它像-Raw一样工作。我已经对它进行了更新和测试。它现在可以通过添加一个-join”-join“n”来工作了。)`语句。还删除了
-ReadCount 0
,因为它毫无意义。谢谢@Matt。你的代码可以工作,但在每个部分前都添加了一行新行,我不知道如何修复它。@juanantonitubío哦……好的,应该可以添加一个
trim
。更新了答案中的正则表达式。再试一次。谢谢@Matt,现在是puttig wh每段末端的ite线;)如果你想测试他,我已经更新了我的gist代码。@Juanantonitubío这些空白存在于你的代码示例中。如果你想删除所有空白,这应该不难。而且你没有显示如何将我的代码导出到文件中。PowerShell在某些情况下会在文件末尾添加一行新行。添加了一些修整器。我可以将你的代码放入其中正如我用来测试:。在这里,我放置了ini.file的第一个版本,运行了3次后得到的结果:。正如您所看到的,每次读取和写入文件时都会放置白线。感谢您的修剪更新,它可以工作,但我不想更改目标文件上的线结构。谢谢@mike,我正在寻找基于正则表达式的解决方案,但您的工作正常这非常完美,是来自Windows PowerShell的API调用的一个很好的例子
 (Get-Content $path -raw) -join "`n" -replace "(?mi)(?<=\[$Section\])([^\[]*$Name=)(.*?)(\r?)$","`$1$Value`$3" | Set-Content $path -NoNewline