Сценарий PowerShell для поиска и замены всех файлов с определенным расширением

124

У меня есть несколько файлов конфигурации на Windows Server 2008, вложенных следующим образом:

C:\Projects\Project_1\project1.config

C:\Projects\Project_2\project2.config

В моей конфигурации мне нужно заменить строку следующим образом:

<add key="Environment" value="Dev"/>

станет:

<add key="Environment" value="Demo"/>

Я думал об использовании пакетного сценария, но не было хорошего способа сделать это, и я слышал, что с помощью сценариев PowerShell вы можете легко это сделать. Я нашел примеры поиска / замены, но я надеялся найти способ, который будет проходить по всем папкам в моем каталоге C: \ Projects и находить любые файлы, которые заканчиваются расширением .config. Когда он его находит, я хочу, чтобы он заменил мои строковые значения.

Есть ли хорошие ресурсы, чтобы узнать, как это сделать, или какие-либо гуру PowerShell, которые могут предложить некоторые идеи?

Brandon
источник
1
Сообщите нам, как у вас обстоят дела и возникли ли какие-то странные проблемы с форматированием файлов, которые необходимо решить. Одна хорошая вещь в этой проблеме заключается в том, что она тестируется, не влияя на производственный код
Robben_Ford_Fan_boy

Ответы:

178

Вот первая попытка в моей голове.

$configFiles = Get-ChildItem . *.config -rec
foreach ($file in $configFiles)
{
    (Get-Content $file.PSPath) |
    Foreach-Object { $_ -replace "Dev", "Demo" } |
    Set-Content $file.PSPath
}
Robben_Ford_Fan_boy
источник
3
Я хотел бы добавить, что тестирование предоставленных решений все работало, но это было самым простым для чтения. Я смог передать это своему коллеге, и он мог легко понять, что происходит. Спасибо за помощь.
Брэндон
11
Чтобы это работало в файлах в подкаталогах, вам нужен ".PSPath". Интересно, что когда я попытался сделать эту работу без () вокруг get-content, у него не получилось записать контент, потому что файл был заблокирован.
Франк Швитерман, 02
25
Краткая версия (используются распространенные псевдонимы):ls *.config -rec | %{ $f=$_; (gc $f.PSPath) | %{ $_ -replace "Dev", "Demo" } | sc $f.PSPath }
Артём
5
@ Артём, не забывай, что .после ls. Меня это ужалило.
Pureferret
5
UnauthorizedAccessException может также вызвать из-за папок, если вы удалите * .config для запуска во всех файлах. Вы можете добавить -File filter к Get-ChildItem ...
Амир Кац
32

PowerShell - хороший выбор;) Очень легко перечислить файлы в заданном каталоге, прочитать их и обработать.

Скрипт мог выглядеть так:

Get-ChildItem C:\Projects *.config -recurse |
    Foreach-Object {
        $c = ($_ | Get-Content) 
        $c = $c -replace '<add key="Environment" value="Dev"/>','<add key="Environment" value="Demo"/>'
        [IO.File]::WriteAllText($_.FullName, ($c -join "`r`n"))
    }

Я разбил код на несколько строк, чтобы вы могли их прочитать. Обратите внимание, что вы можете использовать Set-Content вместо [IO.File]::WriteAllText, но он добавляет новую строку в конце. С ним WriteAllTextможно этого избежать.

В противном случае код может выглядеть следующим образом : $c | Set-Content $_.FullName.

stej
источник
14

Этот подход хорошо работает:

gci C:\Projects *.config -recurse | ForEach {
  (Get-Content $_ | ForEach {$_ -replace "old", "new"}) | Set-Content $_ 
}
  • Измените «старое» и «новое» на их соответствующие значения (или используйте переменные).
  • Не забудьте круглые скобки - без которых вы получите ошибку доступа.
Толга
источник
Поэтому я выбрал это для краткости выражения, но мне пришлось заменить его Get-Content $_на Get-Content $_.FullNameи эквивалент Set-Contentдля обработки файлов, которые не были в корне.
Мэтт Уитфилд
11

Я бы пошел с xml и xpath:

dir C:\Projects\project_*\project*.config -recurse | foreach-object{  
   $wc = [xml](Get-Content $_.fullname)
   $wc.SelectNodes("//add[@key='Environment'][@value='Dev']") | Foreach-Object {$_.value = 'Demo'}  
   $wc.Save($_.fullname)  
}
Шэй Леви
источник
11

Этот пример PowerShell ищет все экземпляры строки «\ foo \» в папке и ее подпапках, заменяет «\ foo \» на «\ bar \» И НЕ ПЕРЕПИСЫВАЕТ файлы, которые не содержат строку «\ foo \» "Таким образом вы не уничтожите отметки даты и времени последнего обновления файла, где строка не была найдена:

Get-ChildItem  -Path C:\YOUR_ROOT_PATH\*.* -recurse 
 | ForEach {If (Get-Content $_.FullName | Select-String -Pattern '\\foo\\') 
           {(Get-Content $_ | ForEach {$_ -replace '\\foo\\', '\bar\'}) | Set-Content $_ }
           }
Kaye
источник
7

Я нашел комментарий @Artyom полезным, но, к сожалению, он не опубликовал ответа.

Это краткая, на мой взгляд, лучшая версия принятого ответа;

ls *.config -rec | %{$f=$_; (gc $f.PSPath) | %{$_ -replace "Dev", "Demo"} | sc $f.PSPath}
M--
источник
1
В случае, если кто-то еще столкнется с этим, как это сделал я - пытаясь выполнить это непосредственно из командного файла - это может помочь использовать foreach-objectвместо %псевдонима при выполнении такой команды. В противном случае это может привести к ошибке:Expressions are only allowed as the first element of a pipeline
Dustin Halstead
Короткая не всегда лучше, понятнее, что происходит в длинной версии.
Ник Н.
@NickN. Ну да ладно. Это зависит от вашей цели. Я бы
пометил
6

Я написал небольшую вспомогательную функцию для замены текста в файле:

function Replace-TextInFile
{
    Param(
        [string]$FilePath,
        [string]$Pattern,
        [string]$Replacement
    )

    [System.IO.File]::WriteAllText(
        $FilePath,
        ([System.IO.File]::ReadAllText($FilePath) -replace $Pattern, $Replacement)
    )
}

Пример:

Get-ChildItem . *.config -rec | ForEach-Object { 
    Replace-TextInFile -FilePath $_ -Pattern 'old' -Replacement 'new' 
}
Мартин Брандл
источник
4

При рекурсивной замене необходимо указать путь и имя файла:

Get-ChildItem -Recurse | ForEach {  (Get-Content $_.PSPath | 
ForEach {$ -creplace "old", "new"}) | Set-Content $_.PSPath }

Это заменит все «старые» на «новые» с учетом регистра во всех файлах ваших папок текущего каталога.

Гейр Ивар Джерстад
источник
Часть вашего ответа ".PSPath" действительно помогла мне. Но мне пришлось изменить внутренний "{$" на "$ _". Я бы отредактировал ваш ответ, но я не использую вашу часть -creplace - я использую принятый ответ с .PSPath
aaaa bbbb