Как я могу заменить каждое вхождение строки в файле с 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
Спасибо - я получаю сообщение об ошибке "replace: не удалось вызвать метод, потому что [System.Object []] не содержит метод с именем 'replace'." хотя?
любитель

\ Как работает escape в PS v4, я только что обнаружил. Спасибо.
ErikE

4
@rob передать результат в set-content или out-file, если вы хотите сохранить модификацию
Loïc MICHEL

2
Я получил ошибку « вызова метода, поскольку [System.Object []] не содержит метод с именем« replace ».» потому что я пытался запустить версию V3 на машине, которая имела только V2.
SFlagg

5
Предупреждение: запуск этих сценариев для больших файлов (пару сотен мегабайт или около того) может израсходовать достаточное количество памяти. Просто убедитесь, что у вас достаточно
свободного

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

Обратите внимание, круглые скобки (Get-Content file.txt)обязательны для заполнения:

Без круглых скобок содержимое читается по одной строке за раз и направляется вниз по конвейеру, пока не достигнет out-file или set-content, который пытается записать в тот же файл, но он уже открыт get-content, и вы получаете ошибка. Скобка заставляет операцию чтения содержимого выполняться один раз (открыть, прочитать и закрыть). Только тогда, когда все строки были прочитаны, они передаются по очереди, и когда они достигают последней команды в конвейере, они могут быть записаны в файл. Это то же самое, что $ content = content; $ контент | где ...


5
Я бы поменял свое голосование на голосование вниз, если бы мог. В PowerShell 3 это автоматически удаляет весь контент из файла! Используя Set-Contentвместо Out-Fileвас, вы получите предупреждение типа «Процесс не может получить доступ к файлу« 123.csv », потому что он используется другим процессом». ,
Иэн Самуэль Маклин, старейшина

9
Это не должно происходить, когда get-content находится в скобках. Они приводят к тому, что операция открывает, читает и закрывает файл, поэтому ошибки не должно быть. Можете ли вы проверить это снова с образцом текстового файла?
Шей Леви

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

6
Проблема с кодировкой файлов UTF-8 . Когда сохраняет файл, изменяет кодировку. Не то же самое. stackoverflow.com/questions/5596982/… . Я думаю, что set-content учитывает файл кодировки (например, UTF-8). но не вне файла
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)

Это имеет преимущество работы с одной строкой вместо массива строк, как с 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находится каталог, содержащий файлы, которые вы хотите изменить.
birdamongmen

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 от Payette в действии . Вы можете ссылаться на файлы, такие как переменные, похожие на $ 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свойство, иначе оно не будет работать в PowerShell версии 5. Мне нужно было изменить целевую версию платформы .NET во ВСЕХ моих 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:

Major.Minor.Build.Revision

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не будет очищать файл, как предложено в этом.
Ciantic
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.