Цикл Foreach в Powershell через XML

#xml #powershell #foreach

#xml #powershell #foreach

Вопрос:

Ниже приведен мой XML

 <step>
    <filename>tk1872_01datanx1872nx1872_base_include_wrn.dat</filename>
    <lookfor>W:\ugs</lookfor>
    <replace>C:BMugs</replace>
</step>
<step>
    <filename>NX1872_01toolscheckReg.ps1</filename>
    <lookfor>W:\ugs</lookfor>
    <replace>C:BMugs</replace>
</step>
<step>
    <filename>tk1872_01datanx1872nx1872_base_include_wrn.dat</filename>
    <lookfor>Y:\CATIAcfgV5</lookfor>
    <replace>C:BMCATIAcfgV5</replace>
</step>
 

Я пытаюсь ссылаться на приведенный выше XML для замены строк в файлах, которые упоминаются в filename node .

Например: я взял ссылку из XML для имени файла и его пути, например (tk1872_01 data nx1872 nx1872_base_include_wrn.dat), затем я ищу «W:ugs « и заменить на «C:BMugs «. Это первый шаг для цикла foreach, но если вы видите 3-й шаг с тем же именем файла, но я ищу другую строку, но цикл foreach не выполнил замену для второго вхождения с тем же именем файла.

Ниже приведен мой код, который я использую в Powershell

 $testXML = C:testsettings.xml
[xml]$SpecialXMLFile = Get-Content $testXML
foreach ($step in $SpecialXMLFile.step)
{
    $_aux = C:temp
    $_aux_dest = D:temp
    $srcfilename = $_aux   ""   $step.filename
    $destfilename = $_aux_dest   ""   $step.filename
    $strun = Get-Content $srcfilename
    $strun -replace $step.lookfor, $step.change | Out-File -Encoding ascii $destfilename
}
 

Не могли бы вы сообщить мне, где я делаю неправильно, или мне нужно добавить еще несколько шагов?

Комментарии:

1. $SpecialXMLFile.configuration.replace.step должен быть $SpecialXMLFile.configuration.step.replace нет?

2. Ваш опубликованный XML-файл не соответствует пути, указанному вами в вашем коде. Отсутствует [конфигурация], и в вашем коде есть элементы, которые не разъясняют, для чего они существуют или существуют, наконец, вы действительно переоцениваете этот вариант использования. XML / JSON является родным для PowerShell и имеет командлеты для работы с ними.

3. @Esperento57 да, извините, у меня есть еще два узла перед шаговыми узлами, о которых я забыл упомянуть

4. @postanote Я использую XML-файл, потому что я не хочу менять код каждый раз, когда я просто вношу изменения в XML, чтобы изменить свой код, и, соответственно, он будет вносить изменения в файлы конфигурации.

5. Итак, вы хотите сказать, что никогда не знаете, что находится в файле XML? Вы можете просто настроить свой код на чтение получаемого вами XML и запрашивать, с каким узлом / значением вы хотите работать. В приведенном мной примере он просто захватывает основной узел конфигурации и отображает узел шага для работы. Затем вы используете эти параметры, чтобы делать то, что хотите. Вы можете запросить что-либо из этого. Никаких изменений в базовом коде не требуется.

Ответ №1:

Продолжая мой комментарий.

 Get-Command -Name '*xml*'
# Results
<#
CommandType Name          Version Source
----------- ----          ------- ------
Cmdlet      ConvertTo-Xml 3.1.0.0 Microsoft.PowerShell.Utility
Cmdlet      Export-Clixml 3.1.0.0 Microsoft.PowerShell.Utility
Cmdlet      Import-Clixml 3.1.0.0 Microsoft.PowerShell.Utility
Cmdlet      Select-Xml    3.1.0.0 Microsoft.PowerShell.Utility
#>

# Get specifics for a module, cmdlet, or function
(Get-Command -Name Select-Xml).Parameters
(Get-Command -Name Select-Xml).Parameters.Keys
Get-help -Name Select-Xml -Examples
Get-help -Name Select-Xml -Full
Get-help -Name Select-Xml -Online
 

Таким образом, почему бы не использовать собственный командлет Select-Xml, чтобы легко выбрать нужный узел и изменить его по мере необходимости.

Пример получения значений шага:

 (@'
<configuration>
    <step>
        <filename>tk1872_01datanx1872_base_include_wrn.dat</filename>
        <lookfor>W:\ugs</lookfor>
        <replace>C:BMugs</replace>
    </step>
    <step>
        <filename>NX1872_01toolscheckReg.ps1</filename>
        <lookfor>W:\ugs</lookfor>
        <replace>C:BMugs</replace>
    </step>
    <step>
        <filename>tk1872_01datanx1872nx1872_base_include_wrn.dat</filename>
        <lookfor>Y:\CATIAcfgV5</lookfor>
        <replace>C:BMCATIAcfgV5</replace>
    </step>
</configuration>
'@  | 
Select-Xml -XPath '//configuration').Node.step
# Results
<#
filename                                          lookfor        replace         
--------                                          -------        -------         
tk1872_01datanx1872_base_include_wrn.dat        W:\ugs        C:BMugs       
NX1872_01toolscheckReg.ps1                      W:\ugs        C:BMugs       
tk1872_01datanx1872nx1872_base_include_wrn.dat Y:\CATIAcfgV5 C:BMCATIAcfgV5
#>
 

Затем замените на что угодно, используя то, что указано.

Комментарии:

1. Не беспокойтесь, это случается. Это просто заставляет нас предполагать / догадываться о том, что происходит на самом деле, хотя это усложняет реагирование. Например, первые две строки в вашем коде являются избыточными, если только вы не запрашиваете это имя файла. $_aux * нигде не определен в вашем коде, поэтому то, что в нем содержится, остается загадкой, а $step.change не имеет ссылки в XML. Итак, покажите больше вашего XML, определения кода и ожидаемого результата.

2. @postnote как я уже сказал, я сожалею, что предоставил короткую информацию. Я снова отредактировал свой вопрос с правильной информацией, как вы просили. Я думаю, что мне не удалось заставить вас понять, что я хотел. моя настоящая проблема заключается в том, что когда я выполняю цикл через шаг в Powershell в выходном файле, он изменяет только первое вхождение, которое <lookfor>W:\ugs</lookfor><replace>C:BMugs</replace></step> не является другим, которое <lookfor>Y:\CATIAcfgV5</lookfor><replace>C:BMCATIAcfgV5</replace>

3. Я хочу сказать, что если имя файла встречается два раза, то оно считается только первым вхождением, а не вторым. это не замена строки во второй раз, когда там есть то же имя файла.

Ответ №2:

Спасибо за дальнейшие разъяснения.

XML, который вы показываете, пропускает корневой элемент, поэтому я использовал это:

 [xml]$xml = @"
<root>
    <step>
        <filename>tk1872_01datanx1872nx1872_base_include_wrn.dat</filename>
        <lookfor>W:\ugs</lookfor>
        <replace>C:BMugs</replace>
    </step>
    <step>
        <filename>NX1872_01toolscheckReg.ps1</filename>
        <lookfor>W:\ugs</lookfor>
        <replace>C:BMugs</replace>
    </step>
    <step>
        <filename>tk1872_01datanx1872nx1872_base_include_wrn.dat</filename>
        <lookfor>Y:\CATIAcfgV5</lookfor>
        <replace>C:BMCATIAcfgV5</replace>
    </step>
</root>
"@
 

В реальной жизни вы бы хотели прочитать это из файла с помощью

 [xml]$xml = Get-Content -Path 'C:testsettings.xml' -Raw
 

Дело в том, что на разных этапах один и тот же файл необходимо настроить, и в вашем коде этого не происходит, потому что файл считывается дважды, чтобы заменить только одно lookfor значение. Во второй раз считывается тот же (неизмененный) исходный файл, и снова выполняется только одна замена.

Что вам нужно сделать, так это сгруппировать шаги, в которых имя файла совпадает, получить содержимое из этого файла только один раз и выполнить все необходимые замены. Затем выводите в новый файл по пути назначения.

 $sourcePath  = 'C:temp'
$destination = 'D:temp'
# combine the steps where the filenames are equal
# if your xml has a different named root, change that here
$xml.root.step | Group-Object filename | ForEach-Object {
    # create the paths from the groups name
    $fileIn  = Join-Path -Path $sourcePath -ChildPath $_.Name
    $fileOut = Join-Path -Path $destination -ChildPath $_.Name
    # test if the output directory path for the file exists. If not create it first
    $pathOut = [System.IO.Path]::GetDirectoryName($fileOut)
    if (!(Test-Path -Path $pathOut -PathType Container)) {
        $null = New-Item -Path $pathOut -ItemType Directory
    }
    # read the input file as a single multiline string
    $content = Get-Content -Path $fileIn -Raw
    foreach ($item in $_.Group) {
        # replace as many times as needed
        $content = $content -replace $item.lookfor, $item.replace
    }
    # are you sure about the -Encoding ascii ??
    $content | Out-File -FilePath $fileOut -Encoding ascii
}