【问题标题】:Powershell and Regex: How to replace a INI name,value pair inside a named section?Powershell 和 Regex:如何在命名部分中替换 INI 名称、值对?
【发布时间】:2015-04-17 00:37:45
【问题描述】:

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

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

我有正则表达式来获取我想要更改的完整部分('sky'):

[[ \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 的 all 实例的代码,我想用新表达式更新 仅替换在给定的部分中

$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

【问题讨论】:

    标签: regex powershell file-io


    【解决方案1】:

    也许另一种解决方案是根本不使用正则表达式并调用 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 函数。

    【讨论】:

    • 谢谢@mike,我正在寻找基于正则表达式的解决方案,但您的解决方案完美无缺,是从 Windows PowerShell 调用 API 的一个很好的例子。你有我的投票:)
    【解决方案2】:

    这是我要做的:将整个文件作为单个字符串读取,然后更改您的正则表达式匹配,使其成为多行匹配,然后对 [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
    

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

    Edit2:-Raw 更改为-ReadCount 0 以实现v2 兼容性。

    Edit3:好吧,我显然记错了。我认为-ReadCount 0 的工作方式与-Raw 相同,但事实并非如此。更新了代码来解决这个问题。现在它用换行符加入字符串数组,基本上把它变成一个here-string,现在-replace按预期工作。

    【讨论】:

    • 感谢@TheMadTechnician,我已经测试过你的,并且在 RegEx101 上工作,但在 poweshell 上没有:( 完整代码要点:gist.github.com/jatubio/5a61ea513cfa7ee93b03
    • 这是我的错,我记错了-ReadCount 0 的工作原理,并认为它像-Raw 一样工作。我已经更新并测试了它。它现在可以通过添加 -join "n"` 语句来工作。还删除了 -ReadCount 0,因为它毫无意义。
    【解决方案3】:

    好的,让我们试试吧。

    @"
    
    #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] 格式的值显然会导致解析无法按预期工作,但结果应该仍然相同。

    测试中的间距问题

    直到 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"
    

    【讨论】:

    • 谢谢@Matt。您的代码有效,但在每个部分之前添加了一个新行,我不知道如何修复它。 gist.github.com/jatubio/428420b2aeca6a954bcc
    • @JuanAntonioTubío 哦....好吧应该可以添加trim。更新了答案中的正则表达式。再试一次。
    • 感谢@Matt,现在每个部分的末尾都有白线;)如果你想测试他,我已经更新了我的 gist 代码。
    • @JuanAntonioTubío 这些空格存在于您的代码示例中。如果你想删除所有不应该很难的空白。此外,您没有显示如何将我的代码导出到文件。在某些情况下,PowerShell 会在文件末尾添加一个新行。添加了一些装饰
    • 这里我把你的代码放在我用来测试的地方:gist.github.com/jatubio/428420b2aeca6a954bcc。在这里,我放置了 ini.file 的第一个版本,运行 3 次后的结果:gist.github.com/jatubio/384d07e58207e7a9ac25/revisions。如您所见,每次读写文件时都会添加白线。感谢您的 Trim 更新,它可以工作,但我不想更改目标文件上的行结构。
    【解决方案4】:
     (Get-Content $path -raw) -join "`n" -replace "(?mi)(?<=\[$Section\])([^\[]*$Name=)(.*?)(\r?)$","`$1$Value`$3" | Set-Content $path -NoNewline
    

    对 TheMadTechnician 的回答稍作改进,以允许保留回车符(如果存在)(并且不添加额外的换行符)。

    【讨论】:

      猜你喜欢
      • 2016-02-04
      • 2016-11-02
      • 2020-04-19
      • 2021-03-04
      • 1970-01-01
      • 1970-01-01
      • 2013-07-24
      • 2019-10-04
      • 2019-10-22
      相关资源
      最近更新 更多