Powershell: добавлять новую строку после каждой строки, содержащей заданную строку

У меня есть 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>

...то есть заменяемый термин определяется как искомая строка, повторенная четыре раза. Поскольку ни одна строка не соответствует этим четырем объединенным строкам, замена не производится.

Мне нужно выполнить замену сразу после нахождения совпадения и прежде, чем переходить ко второму совпадению. Совет оценен!

regex101.com/r/wki1Jo/1 и нажмите «Генератор кода»: regex101.com/r/wki1Jo/1/codegen?language=csharp (надеюсь, csharp во многом соответствует Powershell.... 😉)
Luuk 25.07.2024 19:23

извините, не могу воспроизвести, см.: stackoverflow.com/a/78794724/724039

Luuk 25.07.2024 19:31
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
2
58
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

Невозможно воспроизвести (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>

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

foolishgrunt 25.07.2024 19:57

изменил mre, чтобы было более 1 результата....

Luuk 25.07.2024 20:52
Ответ принят как подходящий

Я нашел свое решение, отказавшись от идеи «-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 25.07.2024 21:00

@foolishgrunt, это означает, что вы забыли передать аргумент -Value. Обратите внимание: для синтаксического распознавания аргумента часть $( должна находиться на той же строке, что и -Value.

mklement0 25.07.2024 21:16

Я скопировал и вставил ваш код в комментарии. :)

foolishgrunt 25.07.2024 21:22

@foolishgrunt, я только что сделал то же самое и в Windows PowerShell, и в PowerShell 7, и код работает так, как ожидалось. Вы можете спровоцировать наблюдаемый симптом следующим образом, доказав, что отсутствие аргумента -Value — это ваша проблема, которой нет в коде этого ответа: Set-Content foo

mklement0 25.07.2024 21:28

Хорошо, я использую PowerShell версии 5. Не знаю, актуально это или нет, но если да, то я, вероятно, не буду этим заниматься. Моя цель с помощью этого сценария — распространить его среди своих коллег для удобного повторного использования, и поскольку я почти уверен, что у моих коллег также есть Powershell 5, я знаю, что некоторые будут жаловаться на необходимость обновления.

foolishgrunt 25.07.2024 21:36

@foolishgrunt: Обновление не требуется: версия 5 (скорее 5.1) — это Windows PowerShell, и код там работает нормально.

mklement0 25.07.2024 21:41

Когда у меня будет немного больше времени (сейчас меня тянут в разных направлениях), я соберу воспроизводимую демонстрацию.

foolishgrunt 30.07.2024 17:27

Другие вопросы по теме

Похожие вопросы

Как извлечь полный идентификатор производителя/поставщика монитора в Windows? (аналогично команде Linux hwinfo --monitor)
Создайте синопсис строки
Я не могу извлечь таблицы .sql CREATE TABLE для каждой таблицы в базе данных SQL Azure в репозитории Azure
Измените OuterXml данного узла, чтобы сохранить его в файле, но сделайте узел бесполезным
Возникает ошибка при попытке отключить конвейер Azure DevOps с помощью Powershell
Как просмотреть все видеофайлы (вид: видео) независимо от того, какое расширение имеют эти файлы (может быть .mkv/.mp4/.mov и т. д.)?
Как преобразовать вывод команды так, чтобы в powershell сохранялись только последние 1000 строк
Get-MgGroupMember по отображаемому имени вместо userID
Привести к сбою конвейера, если в журнале появляется определенное предложение. Найдите подстроку, но игнорируйте определенную подстроку
Родительская ссылка Azure DevOps REST API на существующую ошибку рабочего элемента