Я ищу способ рандомизировать определенную строку в огромном файле, используя предопределенные строки из массива, без необходимости записи временного файла на диск.
Существует файл, который содержит ту же строку, например. «ABC123456789» во многих местах:
<Id>ABC123456789</Id><tag1>some data</tag1><Id>ABC123456789</Id><Id>ABC123456789</Id><tag2>some data</tag2><Id>ABC123456789</Id><tag1>some data</tag1><tag3>some data</tag3><Id>ABC123456789</Id><Id>ABC123456789</Id>
Я пытаюсь рандомизировать эту строку «ABC123456789», используя массив или список определенных строк, например. "@('foo','bar','baz','foo-1','bar-1')". Каждая ABC123456789 должна быть заменена случайно выбранной строкой из массива/списка.
В итоге я получил следующее решение, которое работает «отлично». Но это определенно не правильный подход, так как он сильно экономит на диске - по одному на каждую заменяемую строку и поэтому очень медленный:
$inputFile = Get-Content 'c:\temp\randomize.xml' -raw
$checkString = Get-Content -Path 'c:\temp\randomize.xml' -Raw | Select-String -Pattern '<Id>ABC123456789'
[regex]$pattern = "<Id>ABC123456789"
while($checkString -ne $null) {
$pattern.replace($inputFile, "<Id>$(Get-Random -InputObject @('foo','bar','baz','foo-1','bar-1'))", 1) | Set-Content 'c:\temp\randomize.xml' -NoNewline
$inputFile = Get-Content 'c:\temp\randomize.xml' -raw
$checkString = Get-Content -Path 'c:\temp\randomize.xml' -Raw | Select-String -Pattern '<Id>ABC123456789'
}
Write-Host All finished
Вывод рандомизирован, например:
<Id>foo
<Id>bar
<Id>foo
<Id>foo-1
Однако я хотел бы добиться такого вывода без необходимости записи файла на диск на каждом этапе. Для тысяч вхождений строк требуется много времени. Любая идея, как это сделать?
========================== Редактировать 2023-02-16
Я попробовал решение от zett42, и оно отлично работает с простой структурой XML. В моем случае есть некоторые сложности, которые не были важны в моем подходе к обработке текста. Имена корневого и некоторых других элементов в структуре обрабатываемого XML-файла содержат двоеточие, и для этой ситуации должна быть какая-то специальная настройка для "-XPath". Или, может быть, решение выходит за рамки Powershell.
<?xml version='1.0' encoding='UTF-8'?>
<C23A:SC777a xmlns = "urn:C23A:xsd:$SC777a" xmlns:C23A = "urn:C23A:xsd:$SC777a" xmlns:xsi = "http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation = "urn:C23A:xsd:$SC777a SC777a.xsd">
<C23A:FIToDDD xmlns = "urn:iso:std:iso:20022:tech:xsd:pacs.008.001.02">
<CxAAA>
<DxBBB>
<ABC>
<Id>ZZZZZZ999999</Id>
</ABC>
</DxBBB>
<CxxCCC>
<ABC>
<Id>ABC123456789</Id>
</ABC>
</CxxCCC>
</CxAAA>
<CxAAA>
<DxBBB>
<ABC>
<Id>ZZZZZZ999999</Id>
</ABC>
</DxBBB>
<CxxCCC>
<ABC>
<Id>ABC123456789</Id>
</ABC>
</CxxCCC>
</CxAAA>
</C23A:FIToDDD>
<C23A:PmtRtr xmlns = "urn:iso:std:iso:20022:tech:xsd:pacs.004.001.02">
<GrpHdr>
<TtREEE Abc = "XV">123.45</TtREEE>
<SttlmInf>
<STTm>ABCA</STTm>
<CLss>
<PRta>SIII</PRta>
</CLss>
</SttlmInf>
</GrpHdr>
<TxInf>
<OrgnlTxRef>
<DxBBB>
<ABC>
<Id>YYYYYY888888</Id>
</ABC>
</DxBBB>
<CxxCCC>
<ABC>
<Id>ABC123456789</Id>
</ABC>
</CxxCCC>
</OrgnlTxRef>
</TxInf>
</C23A:PmtRtr>
</C23A:SC777a>
Речь не идет о маскировке данных. У меня есть набор данных в файле xml, который имеет повторяющуюся часть, и мне нужно сделать его менее «однородным» для целей тестирования, в то время как мне нужно использовать предоставленный набор строк для его достижения.
Просмотр и просмотр непосредственно сериализованной строки (например, XML ) с использованием строковых методов (таких как -Replace) — плохая идея. Вместо этого вы должны использовать соответствующий парсер для поиска и замены. См., например: Регулярное выражение Powershell для замены текста между двумя строками
Что касается вашего редактирования, используйте Select-Xml с параметром -Namespace следующим образом: Select-Xml -XPath '//a:Id/text()' -Namespace @{a = 'urn:iso:std:iso:20022:tech:xsd:pacs.008.001.02'}
@ zett42 Я уже сделал это, когда ты упомянул об этом в своем ответе. Я также тестировал код на разных наборах XML, и после изменения «Пространства имен» он работал очень хорошо. Еще раз спасибо.
Как уже отмечалось, не рекомендуется обрабатывать XML как текстовый файл. Это хрупкий подход, который слишком сильно зависит от форматирования XML. Вместо этого используйте правильный анализатор XML для загрузки XML и последующей обработки его элементов объектно-ориентированным способом.
# Use XmlDocument (alias [xml]) to load the XML
$xml = [xml]::new(); $xml.Load(( Convert-Path -LiteralPath input.xml ))
# Define the ID replacements
$searchString = 'ABC123456789'
$replacements = 'foo','bar','baz','foo-1','bar-1'
# Process the text of all ID elements that match the search string, regardless how deeply nested they are.
$xml | Select-Xml -XPath '//Id/text()' | ForEach-Object Node |
Where-Object Value -eq $searchString | ForEach-Object {
# Replace the text of the current element by a randomly choosen string
$_.Value = Get-Random $replacements
}
# Save the modified document to a file
$xml.Save( (New-Item output.xml -Force).Fullname )
$xml | Select-Xml -XPath '//Id/text()'
выбирает текстовые узлы всех Id
элементов, независимо от того, насколько глубоко они вложены в XML DOM, с помощью универсальной команды Select-Xml . Узлы XML выбираются путем указания выражения XPath.
-Namespace
, чтобы указать префикс пространства имен, который будет использоваться в выражении XPath для данного URI пространства имен. В этом примере я просто выбрал a
в качестве префикса пространства имен:
$xml | Select-Xml -XPath '//a:Id/text()' -Namespace @{a = 'urn:iso:std:iso:20022:tech:xsd:pacs.008.001.02'}
ForEach-Object Node
выбирает свойство Node
из каждого результата Select-Xml
. Это упрощает следующий код.Where-Object Value -eq $searchString
выбирает текстовые узлы, соответствующие строке поиска.ForEach-Object
переменная $_
обозначает текущий текстовый узел. Назначьте его свойству Value
изменение текста.Convert-Path
и New-Item
позволяют использовать относительный путь PowerShell (PSPath) с классом .NET XmlDocument
. Как правило, .NET API ничего не знают о текущем каталоге PowerShell, поэтому нам нужно преобразовать пути перед переходом к .NET API.Наконец, я обнаружил, что ваше решение подходит для моей цели, поскольку я могу отделить соответствующую часть кода XML, использовать эту замену, а затем вставить эту часть обратно в окончательный файл XML между открывающей и закрывающей частями. И этот способ синтаксического анализа XML действительно намного быстрее! Спасибо.
Вы пытаетесь выполнить маскирование данных для файла XML?