У меня есть XML-файл >7000 строк, который я определяю как $File
и который содержит (разбросанные по всем остальным строкам) четыре строки следующим образом:
<text:p text:style-name = "P27"><text:span text:style-name = "T20">Page #</text:span><text:span text:style-name = "T19"><text:tab/></text:span><text:span text:style-name = "T20">Content</text:span></text:p>
Сразу под каждой из этих строк я хочу добавить несколько новых строк, которые я определил в отдельном текстовом файле (который я буду вызывать с помощью переменной с названием $Insert
), а затем записать измененные данные обратно в исходный файл. Я считаю, что проще всего идентифицировать эти строки, выполнив поиск по строке «Страница #» (особенно потому, что строки форматирования могут измениться непредвиденным образом при изменении XML).
Я думаю, что проще всего было бы заменить каждую из этих строк одной строкой, состоящей из той же строки, которую я ищу, за которой следует разрыв строки, а затем строки, заданные в переменной $Insert
(хотя я Я открыт для лучших предложений.) Вот код, который я разработал на данный момент (и для этого примера я определю $Insert
непосредственно как содержащий строку 1 = foo
и строку 2 = bar
):
$Insert = "foo`nbar"
$Locate = Get-Content $File | Select-String "Page #" | Select-Object -ExpandProperty Line
(Get-Content $File) |ForEach-Object {$_ -replace $Locate,"$Locate`n$Insert"} | Set-Content $File
Я хочу, чтобы каждый экземпляр искомой строки был заменен следующим:
<text:p text:style-name = "P27"><text:span text:style-name = "T20">Page #</text:span><text:span text:style-name = "T19"><text:tab/></text:span><text:span text:style-name = "T20">Content</text:span></text:p>
foo
bar
Вместо этого, очевидно, никакой замены не происходит. Запуск echo $Locate
дает следующий результат:
<text:p text:style-name = "P27"><text:span text:style-name = "T20">Page #</text:span><text:span text:style-name = "T19"><text:tab/></text:span><text:span text:style-name = "T
20">Content</text:span></text:p> <text:p text:style-name = "P27"><text:span text:style-name = "T20">Page #</text:span><text:span text:style-name = "T19"><text:tab/></text:s
pan><text:span text:style-name = "T20">Content</text:span></text:p> <text:p text:style-name = "P27"><text:span text:style-name = "T20">Page #</text:span><text:span text:sty
le-name = "T19"><text:tab/></text:span><text:span text:style-name = "T20">Content</text:span></text:p> <text:p text:style-name = "P27"><text:span text:style-name = "T20">Page
#</text:span><text:span text:style-name = "T19"><text:tab/></text:span><text:span text:style-name = "T20">Content</text:span></text:p>
...то есть заменяемый термин определяется как искомая строка, повторенная четыре раза. Поскольку ни одна строка не соответствует этим четырем объединенным строкам, замена не производится.
Мне нужно выполнить замену сразу после нахождения совпадения и прежде, чем переходить ко второму совпадению. Совет оценен!
извините, не могу воспроизвести, см.: stackoverflow.com/a/78794724/724039
Невозможно воспроизвести (mre):
PS D:\temp> get-content .\abc.csv
"a","b","c"
1,2,3
4,5,replace
7,8,9
1,2,3
4,5,replace
7,8,9
PS D:\temp> (Get-Content .\abc.csv) |ForEach-Object {$_ -replace 'replace',"replace`nfoor`nbar`n"} | Set-Content .\abc.csv
PS D:\temp> get-content .\abc.csv
"a","b","c"
1,2,3
4,5,replace
foor
bar
7,8,9
1,2,3
4,5,replace
foor
bar
7,8,9
PS D:\temp>
Во-первых, вы заменяете только поисковый запрос, а не всю строку, содержащую поисковый запрос. Во-вторых, в исходном файле есть только один экземпляр поискового запроса, и мой код работает только для одного совпадения с поисковым запросом. Это терпит неудачу, когда их больше одного.
изменил mre, чтобы было более 1 результата....
Я нашел свое решение, отказавшись от идеи «-replace». Новый код:
$Insert = "foo`nbar"
(Get-Content $File) | Foreach-Object { $_ # Pass each line of file through
if ($_ -match "Page #") { #If the search string is located in this line...
$Insert} #...insert new lines below
} | Set-Content $File #Write all modified content to the file
В результате в файле появляются следующие строки:
<text:p text:style-name = "P27"><text:span text:style-name = "T20">Page #</text:span><text:span text:style-name = "T19"><text:tab/></text:span><text:span text:style-name = "T20">Content</text:span></text:p>
foo
bar
...везде в файле, где встречается поисковый запрос.
Ваше собственное решение работает, но я предлагаю использовать оператор switch для более быстрой построчной обработки вашего файла с помощью -File
и сопоставления регулярных выражений с -Regex
:
# On Windows, it is better to use:
# "foo`r`nbar"
$Insert = "foo`nbar"
Set-Content $File -Value $( # NOTE: Be sure to place $( on the same line as -Value
switch -File $File -Regex {
'Page #' { $_; $Insert } # pass through, followed by lines to insert
default { $_ } # pass through
}
)
Как и в вашем решении, измененный контент записывается непосредственно обратно во входной файл, поэтому рекомендуется сначала сделать резервную копию.
Чтобы иметь возможность заменить содержимое входного файла на основе исходного содержимого, последнее необходимо сначала полностью прочитать в памяти, как в вашем решении. (Здесь $(...)
обеспечивает предварительную обработку всего файла; в вашем решении это (...)
вокруг вашего Get-Content
вызова).
Предостережение относительно формата новой строки: в вашем определении $Insert
вы используете `n
, т. е. новую строку в формате Unix только для LF; если вы запускаете код в Windows, сквозные строки будут разделены символами новой строки CRLF в формате Windows; чтобы избежать смешивания разных форматов новой строки в файле, либо используйте `r`n
в Windows, либо используйте абстракцию, которая обеспечивает новую строку, соответствующую хост-платформе, [Environment]::NewLine
, например 'foo{0}bar' -f [Environment]::NewLine
Предостережение относительно кодировки символов: switch
при передаче файла без спецификации файл всегда интерпретируется как файл в кодировке UTF-8; хотя этого и следовало ожидать в PowerShell (Core) 7, где по умолчанию постоянно используется UTF-8, в Windows PowerShell это может показаться удивительным, учитывая, что Get-Content
там по умолчанию используется ANSI (кодовая страница ANSI, связанная с устаревшим языковым стандартом системы). ).
Когда я запускаю предложенную вами команду, сценарий останавливается и предлагает мне Supply values for the following parameters: Value[0]:
@foolishgrunt, это означает, что вы забыли передать аргумент -Value
. Обратите внимание: для синтаксического распознавания аргумента часть $(
должна находиться на той же строке, что и -Value
.
Я скопировал и вставил ваш код в комментарии. :)
@foolishgrunt, я только что сделал то же самое и в Windows PowerShell, и в PowerShell 7, и код работает так, как ожидалось. Вы можете спровоцировать наблюдаемый симптом следующим образом, доказав, что отсутствие аргумента -Value
— это ваша проблема, которой нет в коде этого ответа: Set-Content foo
Хорошо, я использую PowerShell версии 5. Не знаю, актуально это или нет, но если да, то я, вероятно, не буду этим заниматься. Моя цель с помощью этого сценария — распространить его среди своих коллег для удобного повторного использования, и поскольку я почти уверен, что у моих коллег также есть Powershell 5, я знаю, что некоторые будут жаловаться на необходимость обновления.
@foolishgrunt: Обновление не требуется: версия 5 (скорее 5.1) — это Windows PowerShell, и код там работает нормально.
Когда у меня будет немного больше времени (сейчас меня тянут в разных направлениях), я соберу воспроизводимую демонстрацию.