Використовуючи PowerShell, я хочу замінити всі точні події [MYID]в даному файлі на MyValue. Який найпростіший спосіб зробити це?
Використовуючи PowerShell, я хочу замінити всі точні події [MYID]в даному файлі на MyValue. Який найпростіший спосіб зробити це?
Відповіді:
Використання (версія V3):
(Get-Content c:\temp\test.txt).replace('[MYID]', 'MyValue') | Set-Content c:\temp\test.txt
Або для V2:
(Get-Content c:\temp\test.txt) -replace '\[MYID\]', 'MyValue' | Set-Content c:\temp\test.txt
(Get-Content file.txt) |
Foreach-Object {$_ -replace '\[MYID\]','MyValue'} |
Out-File file.txt
Зверніть увагу, що навколо (Get-Content file.txt)потрібні дужки :
Без дужок зміст зчитується по одному рядку і протікає вниз по конвеєру до тих пір, поки не потрапить у файл-файл або контент-вміст, який намагається записати в той самий файл, але він вже відкритий get-content і ви отримуєте помилка. Дужки призводять до того, що операція зчитування вмісту повинна бути виконана один раз (відкрити, прочитати та закрити). Тільки тоді, коли всі рядки прочитані, вони прокладаються по одному, і коли вони дістають останню команду в конвеєрі, їх можна записати у файл. Це те саме, що $ content = content; $ вміст | де ...
Set-Contentзамість Out-Fileyuou, ви отримуєте попередження типу "Процес не може отримати доступ до файлу" 123.csv ", оскільки він використовується іншим процесом." .
Get-Contentдужками це працює. Чи можете ви пояснити у своїй відповіді, чому потрібні дужки? Я б ще замінити Out-Fileз , Set-Contentтому що це безпечніше; він захищає вас від витирання цільового файлу, якщо ви забудете дужки.
Set-Contentзамість цього Out-Fileнабагато краще та безпечніше рішення. Вибачте, потрібно подати заявку.
Я вважаю за краще використовувати клас File .NET і його статичні методи, як показано в наступному прикладі.
$content = [System.IO.File]::ReadAllText("c:\bla.txt").Replace("[MYID]","MyValue")
[System.IO.File]::WriteAllText("c:\bla.txt", $content)
Це має перевагу в роботі з одним String замість масиву String, як із Get-Content . Методи також забезпечують кодування файлу (UTF-8 BOM тощо), без того, щоб вам доводилося піклуватися більшу частину часу.
Крім того, методи не псують закінчення рядків (закінчення рядків Unix, які можуть бути використані) на відміну від алгоритму, що використовує Get-Content і пропускає через Set-Content .
Тож для мене: менше речей, які можуть зламатися роками.
Маловідома при використанні класів .NET полягає в тому, що, коли ви ввели "[System.IO.File] ::" у вікні PowerShell, ви можете натиснути Tabклавішу, щоб перейти до методів, які існують там.
[System.IO.File] | gm
C:\Windows\System32\WindowsPowerShell\v1.0?
Описаний вище працює лише для "одного файлу", але ви також можете запустити це для кількох файлів у вашій папці:
Get-ChildItem 'C:yourfile*.xml' -Recurse | ForEach {
(Get-Content $_ | ForEach { $_ -replace '[MYID]', 'MyValue' }) |
Set-Content $_
}
foreachможна зробити цеGet-ChildItem 'C:\folder\file*.xml' -Recurse | ForEach { (Get-Content $_).Replace('[MYID]', 'MyValue') | Set-Content $_ }
foreach, тому що Get-Content робить те, чого ви можете не очікувати ... Він повертає масив рядків, де кожна рядок є рядком у файлі. Якщо ви переглядаєте каталог (і підкаталоги), які знаходяться в іншому місці, ніж ваш запущений сценарій, вам потрібно щось подібне: Get-ChildItem $Directory -File -Recurse | ForEach { (Get-Content $_.FullName) | ForEach { $_ -replace '[MYID]', 'MyValue' } | Set-Content $_.FullName }де $Directoryзнаходиться каталог, що містить файли, які ви хочете змінити.
Це те, що я використовую, але на великих текстових файлах це повільно.
get-content $pathToFile | % { $_ -replace $stringToReplace, $replaceWith } | set-content $pathToFile
Якщо ви збираєтеся замінювати рядки у великих текстових файлах, і швидкість викликає занепокоєння, зверніть увагу на використання System.IO.StreamReader та System.IO.StreamWriter .
try
{
$reader = [System.IO.StreamReader] $pathToFile
$data = $reader.ReadToEnd()
$reader.close()
}
finally
{
if ($reader -ne $null)
{
$reader.dispose()
}
}
$data = $data -replace $stringToReplace, $replaceWith
try
{
$writer = [System.IO.StreamWriter] $pathToFile
$writer.write($data)
$writer.close()
}
finally
{
if ($writer -ne $null)
{
$writer.dispose()
}
}
(Код вище не перевірений.)
Мабуть, є більш елегантний спосіб використання StreamReader і StreamWriter для заміни тексту в документі, але це повинно дати вам хорошу вихідну точку.
Я знайшов маловідомий, але дивовижно класний спосіб зробити це з Windows Powershell в дії . Ви можете посилатися на файли, як на змінні, схожі на $ env: path, але вам потрібно додати фігурні дужки.
${c:file.txt} = ${c:file.txt} -replace 'oldvalue','newvalue'
$myFile?
$a = 'file.txt'; invoke-expression "`${c:$a} = `${c:$a} -replace 'oldvalue','newvalue'"
Якщо вам потрібно замінити рядки в декількох файлах:
Слід зазначити, що різні способи, розміщені тут, можуть бути різно різними щодо часу, необхідного для його завершення. Для мене регулярно є велика кількість невеликих файлів. Щоб перевірити, що найефективніше, я витягнув 5,52 ГБ (5,933,604,999 байт) XML у 40 693 окремих файлів і пробіг три відповіді, які я знайшов тут:
## 5.52 GB (5,933,604,999 bytes) of XML files (40,693 files)
#### Test 1 - Plain Replace
$start = get-date
$xmls = (Get-ChildItem -Path "I:\TestseT\All_XML" -Recurse -Filter *.xml).FullName
foreach ($xml in $xmls)
{
(Get-Content $xml).replace("'", " ") | Set-Content $xml
}
$end = get-date
NEW-TIMESPAN –Start $Start –End $End
<#
TotalMinutes: 103.725113128333
#>
#### Test 2 - Replace with -Raw
$start = get-date
$xmls = (Get-ChildItem -Path "I:\TestseT\All_XML" -Recurse -Filter *.xml).FullName
foreach ($xml in $xmls)
{
(Get-Content $xml -Raw).replace("'", " ") | Set-Content $xml
}
$end = get-date
NEW-TIMESPAN –Start $Start –End $End
<#
TotalMinutes: 10.1600227983333
#>
#### Test 3 - .NET, System.IO
$start = get-date
$xmls = (Get-ChildItem -Path "I:\TestseT\All_XML" -Recurse -Filter *.xml).FullName
foreach ($xml in $xmls)
{
$txt = [System.IO.File]::ReadAllText("$xml").Replace("'"," ")
[System.IO.File]::WriteAllText("$xml", $txt)
}
$end = get-date
NEW-TIMESPAN –Start $Start –End $End
<#
TotalMinutes: 5.83619516833333
#>
Кредит на @ rominator007
Я перетворив його на функцію (тому що ви можете скористатися нею знову)
function Replace-AllStringsInFile($SearchString,$ReplaceString,$FullPathToFile)
{
$content = [System.IO.File]::ReadAllText("$FullPathToFile").Replace("$SearchString","$ReplaceString")
[System.IO.File]::WriteAllText("$FullPathToFile", $content)
}
ПРИМІТКА: Це НЕ чутливе до регістру !!!!!
Дивіться цю публікацію: String.Замініть ігнорування випадку
Це працювало для мене, використовуючи поточний робочий каталог в PowerShell. Вам потрібно скористатися FullNameвластивістю, інакше вона не працюватиме у версії 5. PowerShell. Мені потрібно було змінити цільову версію .NET Framework у ВСІХ моїх CSPROJфайлах.
gci -Recurse -Filter *.csproj |
% { (get-content "$($_.FullName)")
.Replace('<TargetFramework>net47</TargetFramework>', '<TargetFramework>net462</TargetFramework>') |
Set-Content "$($_.FullName)"}
Трохи старий і інший, як мені потрібно було змінити певний рядок у всіх примірниках певного імені файлу.
Крім того, Set-Contentне було повернення послідовних результатів, тому мені довелося вдатися Out-File.
Код нижче:
$FileName =''
$OldLine = ''
$NewLine = ''
$Drives = Get-PSDrive -PSProvider FileSystem
foreach ($Drive in $Drives) {
Push-Location $Drive.Root
Get-ChildItem -Filter "$FileName" -Recurse | ForEach {
(Get-Content $_.FullName).Replace($OldLine, $NewLine) | Out-File $_.FullName
}
Pop-Location
}
Ось що найкраще працювало для мене у цій версії PowerShell:
Майор.Мінор.Бул.Ревізія
5.1.16299.98
Невелика корекція команди Set-Content. Якщо шуканий рядок не знайдений, Set-Contentкоманда буде порожнім (порожнім) цільовий файл.
Ви можете спочатку перевірити, існує чи ні шукана рядок. Якщо ні, то це нічого не замінить.
If (select-string -path "c:\Windows\System32\drivers\etc\hosts" -pattern "String to look for") `
{(Get-Content c:\Windows\System32\drivers\etc\hosts).replace('String to look for', 'String to replace with') | Set-Content c:\Windows\System32\drivers\etc\hosts}
Else{"Nothing happened"}
set-content test.txt "hello hello world hello world hello"а потім (get-content .\test.txt).Replace("something", "awesome") | set-content .\test.txtне буде порожнім файлом, як запропоновано в цьому.