Як я можу замінити кожне виникнення рядка у файлі на PowerShell?


289

Використовуючи PowerShell, я хочу замінити всі точні події [MYID]в даному файлі на MyValue. Який найпростіший спосіб зробити це?


Більш ефективне рішення щодо споживання пам'яті, ніж запропоновано у відповідях на це запитання, див. Знайти та замінити у великому файлі .
Мартін Прикрил

Відповіді:


444

Використання (версія 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

3
Дякую - я отримую помилку "Замінити: Виклик методу не вдалося, оскільки [System.Object []] не містить методу з назвою" Замінити ". хоч?
любитель

\ як працює втеча в PS v4, який я щойно знайшов. Дякую.
ErikE

4
@rob передайте результат у встановлений контент чи файл у вихід, якщо ви хочете зберегти модифікацію
Loïc MICHEL

2
У мене виникла помилка "Не вдалося викликати метод, оскільки [System.Object []] не містить методу з назвою" Замінити ". тому що я намагався запустити версію V3 на машині, на якій був лише V2.
SFlagg

5
Попередження: Запуск цих скриптів на великих файлах (пару сотень мегабайт або близько того) може з'їсти неабияку кількість пам'яті. Просто будьте впевнені, що у вас є достатньо місця для головної роботи, якщо ви працюєте на виробничому сервері: D
відписатись

89
(Get-Content file.txt) | 
Foreach-Object {$_ -replace '\[MYID\]','MyValue'}  | 
Out-File file.txt

Зверніть увагу, що навколо (Get-Content file.txt)потрібні дужки :

Без дужок зміст зчитується по одному рядку і протікає вниз по конвеєру до тих пір, поки не потрапить у файл-файл або контент-вміст, який намагається записати в той самий файл, але він вже відкритий get-content і ви отримуєте помилка. Дужки призводять до того, що операція зчитування вмісту повинна бути виконана один раз (відкрити, прочитати та закрити). Тільки тоді, коли всі рядки прочитані, вони прокладаються по одному, і коли вони дістають останню команду в конвеєрі, їх можна записати у файл. Це те саме, що $ content = content; $ вміст | де ...


5
Якби я міг, я міняв би свій внесок на потік. У PowerShell 3 це мовчки видаляє весь вміст з файлу! Використовуючи Set-Contentзамість Out-Fileyuou, ви отримуєте попередження типу "Процес не може отримати доступ до файлу" 123.csv ", оскільки він використовується іншим процесом." .
Ієн Самуель Маклін Старший

9
Це не повинно відбуватися, коли get-content знаходиться в дужках. Вони змушують операцію відкриватись, читати та закривати файл, щоб помилка, яку ви отримали, не мала трапитися. Чи можете ви перевірити його ще раз за допомогою зразка текстового файлу?
Шей Леві

2
З Get-Contentдужками це працює. Чи можете ви пояснити у своїй відповіді, чому потрібні дужки? Я б ще замінити Out-Fileз , Set-Contentтому що це безпечніше; він захищає вас від витирання цільового файлу, якщо ви забудете дужки.
Ієн Самуель Маклін Старший

6
Проблема з кодуванням файлів UTF-8 . Коли файл зберігається, змінюється кодування. Не те ж саме. stackoverflow.com/questions/5596982/… . Я думаю, що set-content розглядає файл кодування (як UTF-8). але не Out-File
Kiquenet

1
Це рішення є надмірно оманливим і спричиняє проблеми, коли я ним користувався. Я оновлював конфігураційний файл, який безпосередньо використовувався в процесі встановлення. Конфігураційний файл все ще зберігався процесом, і встановлення не вдалося. Використовувати Set-Contentзамість цього Out-Fileнабагато краще та безпечніше рішення. Вибачте, потрібно подати заявку.
Мартін Басіста

81

Я вважаю за краще використовувати клас 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
fbehrens

Чому цей метод бере на себе відносний шлях C:\Windows\System32\WindowsPowerShell\v1.0?
Адріан

Невже це так? Це, мабуть, має щось спільне з тим, як запускається .NET AppDomain в PowerShell. Можливо, поточний шлях не оновлюється при використанні CD. Але це не більше, ніж освічена здогадка. Я цього не перевіряв і не шукав.
rominator007

2
Це також набагато простіше, ніж писати різний код для різних версій Powershell.
Віллем ван Кетвіч

Цей метод також виявляється найшвидшим. У поєднанні з зазначеними перевагами і слід поставити запитання: "навіщо ви хочете використовувати щось інше?"
DBADon

21

Описаний вище працює лише для "одного файлу", але ви також можете запустити це для кількох файлів у вашій папці:

Get-ChildItem 'C:yourfile*.xml' -Recurse | ForEach {
     (Get-Content $_ | ForEach  { $_ -replace '[MYID]', 'MyValue' }) |
     Set-Content $_
}

зауважте, що я використовував .xml, але ви можете замінити його на .txt
Джон V Хоббс-молодший

Приємно. Як варіант використання внутрішнього foreachможна зробити цеGet-ChildItem 'C:\folder\file*.xml' -Recurse | ForEach { (Get-Content $_).Replace('[MYID]', 'MyValue') | Set-Content $_ }
KCD

1
Насправді вам потрібен внутрішній foreach, тому що Get-Content робить те, чого ви можете не очікувати ... Він повертає масив рядків, де кожна рядок є рядком у файлі. Якщо ви переглядаєте каталог (і підкаталоги), які знаходяться в іншому місці, ніж ваш запущений сценарій, вам потрібно щось подібне: Get-ChildItem $Directory -File -Recurse | ForEach { (Get-Content $_.FullName) | ForEach { $_ -replace '[MYID]', 'MyValue' } | Set-Content $_.FullName }де $Directoryзнаходиться каталог, що містить файли, які ви хочете змінити.
пташині молодики

1
Яка відповідь - «та, що вище»?
Пітер Мортенсен

10

Ви можете спробувати щось подібне:

$path = "C:\testFile.txt"
$word = "searchword"
$replacement = "ReplacementText"
$text = get-content $path 
$newText = $text -replace $word,$replacement
$newText > $path

7

Це те, що я використовую, але на великих текстових файлах це повільно.

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 для заміни тексту в документі, але це повинно дати вам хорошу вихідну точку.


Я думаю, що set-content розглядає файл кодування (як UTF-8). але не Out-File stackoverflow.com/questions/5596982 / ...
Kiquenet

2

Я знайшов маловідомий, але дивовижно класний спосіб зробити це з Windows Powershell в дії . Ви можете посилатися на файли, як на змінні, схожі на $ env: path, але вам потрібно додати фігурні дужки.

${c:file.txt} = ${c:file.txt} -replace 'oldvalue','newvalue'

Що робити, якщо ім'я файлу є такою змінною, як $myFile?
ΩmegaMan

@ ΩmegaMan хм, лише це поки$a = 'file.txt'; invoke-expression "`${c:$a} = `${c:$a} -replace 'oldvalue','newvalue'"
js2010

2

Якщо вам потрібно замінити рядки в декількох файлах:

Слід зазначити, що різні способи, розміщені тут, можуть бути різно різними щодо часу, необхідного для його завершення. Для мене регулярно є велика кількість невеликих файлів. Щоб перевірити, що найефективніше, я витягнув 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
#>

Питання полягало у заміні рядків у заданому файлі, а не кількох файлах.
PL

1

Кредит на @ rominator007

Я перетворив його на функцію (тому що ви можете скористатися нею знову)

function Replace-AllStringsInFile($SearchString,$ReplaceString,$FullPathToFile)
{
    $content = [System.IO.File]::ReadAllText("$FullPathToFile").Replace("$SearchString","$ReplaceString")
    [System.IO.File]::WriteAllText("$FullPathToFile", $content)
}

ПРИМІТКА: Це НЕ чутливе до регістру !!!!!

Дивіться цю публікацію: String.Замініть ігнорування випадку


0

Це працювало для мене, використовуючи поточний робочий каталог в PowerShell. Вам потрібно скористатися FullNameвластивістю, інакше вона не працюватиме у версії 5. PowerShell. Мені потрібно було змінити цільову версію .NET Framework у ВСІХ моїх CSPROJфайлах.

gci -Recurse -Filter *.csproj |
% { (get-content "$($_.FullName)")
.Replace('<TargetFramework>net47</TargetFramework>', '<TargetFramework>net462</TargetFramework>') |
 Set-Content "$($_.FullName)"}

0

Трохи старий і інший, як мені потрібно було змінити певний рядок у всіх примірниках певного імені файлу.

Крім того, 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


-1

Невелика корекція команди 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"}

3
Ласкаво просимо до StackOverflow! Будь ласка, використовуйте форматування, ви можете прочитати цю статтю, якщо вам потрібна допомога.
CodenameLambda

1
Це неправда, якщо використовується правильна відповідь і заміна не знайдена, вона все одно записує файл, але змін немає. Наприклад, set-content test.txt "hello hello world hello world hello"а потім (get-content .\test.txt).Replace("something", "awesome") | set-content .\test.txtне буде порожнім файлом, як запропоновано в цьому.
Сіантік
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.