Используя 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)
обязательны для заполнения:
Без круглых скобок содержимое читается по одной строке за раз и направляется вниз по конвейеру, пока не достигнет out-file или set-content, который пытается записать в тот же файл, но он уже открыт get-content, и вы получаете ошибка. Скобка заставляет операцию чтения содержимого выполняться один раз (открыть, прочитать и закрыть). Только тогда, когда все строки были прочитаны, они передаются по очереди, и когда они достигают последней команды в конвейере, они могут быть записаны в файл. Это то же самое, что $ content = content; $ контент | где ...
Set-Content
вместо Out-File
вас, вы получите предупреждение типа «Процесс не может получить доступ к файлу« 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)
Это имеет преимущество работы с одной строкой вместо массива строк, как с 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 от Payette в действии . Вы можете ссылаться на файлы, такие как переменные, похожие на $ 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
свойство, иначе оно не будет работать в PowerShell версии 5. Мне нужно было изменить целевую версию платформы .NET во ВСЕХ моих 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:
Major.Minor.Build.Revision
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
не будет очищать файл, как предложено в этом.