Sử dụng PowerShell, tôi muốn thay thế tất cả các lần xuất hiện chính xác [MYID]
trong một tệp đã cho bằng MyValue
. Cách dễ nhất để làm như vậy là gì?
Sử dụng PowerShell, tôi muốn thay thế tất cả các lần xuất hiện chính xác [MYID]
trong một tệp đã cho bằng MyValue
. Cách dễ nhất để làm như vậy là gì?
Câu trả lời:
Sử dụng (phiên bản V3):
(Get-Content c:\temp\test.txt).replace('[MYID]', 'MyValue') | Set-Content c:\temp\test.txt
Hoặc cho 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
Lưu ý dấu ngoặc đơn xung quanh (Get-Content file.txt)
là bắt buộc:
Không có dấu ngoặc đơn, nội dung được đọc, mỗi dòng một dòng và chảy xuống đường ống cho đến khi nó đến tập tin ngoài hoặc tập hợp nội dung, cố gắng ghi vào cùng một tệp, nhưng nó đã được mở bằng nội dung get và bạn nhận được một lỗi. Dấu ngoặc đơn làm cho hoạt động đọc nội dung được thực hiện một lần (mở, đọc và đóng). Chỉ sau đó khi tất cả các dòng đã được đọc, chúng được dẫn từng dòng một và khi chúng đạt đến lệnh cuối cùng trong đường ống, chúng có thể được ghi vào tệp. Nó giống như $ content = content; $ nội dung | Ở đâu ...
Set-Content
thay vì Out-File
yuou nhận được cảnh báo như "Quá trình không thể truy cập tệp '123.csv' vì nó đang được sử dụng bởi một quy trình khác." .
Get-Content
trong ngoặc đơn nó hoạt động. Bạn có thể giải thích trong câu trả lời của bạn tại sao dấu ngoặc đơn là cần thiết? Tôi vẫn sẽ thay thế Out-File
với Set-Content
vì nó an toàn; nó bảo vệ bạn khỏi xóa tệp đích nếu bạn quên dấu ngoặc đơn.
Set-Content
thay vì Out-File
là giải pháp tốt hơn và an toàn hơn nhiều. Xin lỗi phải downvote.
Tôi thích sử dụng lớp File của .NET và các phương thức tĩnh của nó như trong ví dụ sau.
$content = [System.IO.File]::ReadAllText("c:\bla.txt").Replace("[MYID]","MyValue")
[System.IO.File]::WriteAllText("c:\bla.txt", $content)
Điều này có lợi thế là làm việc với một Chuỗi thay vì một chuỗi Chuỗi như với Nội dung . Các phương pháp cũng đảm nhiệm việc mã hóa tệp (UTF-8 BOM , v.v.) mà bạn không phải chăm sóc hầu hết thời gian.
Ngoài ra, các phương thức không làm rối các kết thúc dòng (kết thúc dòng Unix có thể được sử dụng) trái ngược với thuật toán sử dụng Get-Content và chuyển qua Set-Content .
Vì vậy, đối với tôi: Ít thứ có thể phá vỡ trong những năm qua.
Một điều ít được biết đến khi sử dụng các lớp .NET là khi bạn đã gõ "[System.IO.File] ::" trong cửa sổ PowerShell, bạn có thể nhấn Tabphím để xem qua các phương thức ở đó.
[System.IO.File] | gm
C:\Windows\System32\WindowsPowerShell\v1.0
?
Tệp ở trên chỉ chạy cho "Một tệp", nhưng bạn cũng có thể chạy tệp này cho nhiều tệp trong thư mục của mình:
Get-ChildItem 'C:yourfile*.xml' -Recurse | ForEach {
(Get-Content $_ | ForEach { $_ -replace '[MYID]', 'MyValue' }) |
Set-Content $_
}
foreach
bạn có thể thực hiện việc nàyGet-ChildItem 'C:\folder\file*.xml' -Recurse | ForEach { (Get-Content $_).Replace('[MYID]', 'MyValue') | Set-Content $_ }
foreach
, bởi vì Get-Content thực hiện điều mà bạn có thể không mong đợi ... Nó trả về một chuỗi các chuỗi, trong đó mỗi chuỗi là một dòng trong tệp. Nếu bạn đang lặp qua một thư mục (và thư mục con) ở một vị trí khác với tập lệnh đang chạy của bạn, bạn sẽ muốn một cái gì đó như thế này: Get-ChildItem $Directory -File -Recurse | ForEach { (Get-Content $_.FullName) | ForEach { $_ -replace '[MYID]', 'MyValue' } | Set-Content $_.FullName }
đâu $Directory
là thư mục chứa các tệp bạn muốn sửa đổi.
Đây là những gì tôi sử dụng, nhưng nó chậm trên các tệp văn bản lớn.
get-content $pathToFile | % { $_ -replace $stringToReplace, $replaceWith } | set-content $pathToFile
Nếu bạn sẽ thay thế các chuỗi trong các tệp văn bản lớn và tốc độ là một mối quan tâm, hãy xem xét sử dụng System.IO.StreamReader và 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()
}
}
(Đoạn mã trên chưa được kiểm tra.)
Có lẽ có một cách thanh lịch hơn để sử dụng StreamReader và StreamWriter để thay thế văn bản trong tài liệu, nhưng điều đó sẽ cho bạn một điểm khởi đầu tốt.
Tôi đã tìm thấy một cách ít được biết đến nhưng tuyệt vời để làm điều đó từ Windows Powershell in Action của Payette . Bạn có thể tham chiếu các tệp như biến, tương tự như $ env: path, nhưng bạn cần thêm dấu ngoặc nhọn.
${c:file.txt} = ${c:file.txt} -replace 'oldvalue','newvalue'
$myFile
?
$a = 'file.txt'; invoke-expression "`${c:$a} = `${c:$a} -replace 'oldvalue','newvalue'"
Nếu bạn cần thay thế chuỗi trong nhiều tệp:
Cần lưu ý rằng các phương pháp khác nhau được đăng ở đây có thể rất khác nhau về thời gian cần thiết để hoàn thành. Đối với tôi, tôi thường xuyên có số lượng lớn các tập tin nhỏ. Để kiểm tra những gì hiệu quả nhất, tôi đã trích xuất 5,52 GB (5.933.604.999 byte) XML trong 40.693 tệp riêng biệt và chạy qua ba trong số các câu trả lời tôi tìm thấy ở đây:
## 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
#>
Tín dụng cho @ rominator007
Tôi gói nó vào một chức năng (vì bạn có thể muốn sử dụng lại)
function Replace-AllStringsInFile($SearchString,$ReplaceString,$FullPathToFile)
{
$content = [System.IO.File]::ReadAllText("$FullPathToFile").Replace("$SearchString","$ReplaceString")
[System.IO.File]::WriteAllText("$FullPathToFile", $content)
}
LƯU Ý: Đây không phải là trường hợp nhạy cảm !!!!!
Xem bài đăng này: String.Replace trường hợp bỏ qua
Điều này làm việc cho tôi bằng cách sử dụng thư mục làm việc hiện tại trong PowerShell. Bạn cần sử dụng thuộc FullName
tính hoặc nó sẽ không hoạt động trong phiên bản PowerShell 5. Tôi cần thay đổi phiên bản khung .NET đích trong TẤT CẢ CSPROJ
các tệp của mình .
gci -Recurse -Filter *.csproj |
% { (get-content "$($_.FullName)")
.Replace('<TargetFramework>net47</TargetFramework>', '<TargetFramework>net462</TargetFramework>') |
Set-Content "$($_.FullName)"}
Một chút cũ và khác nhau, vì tôi cần thay đổi một dòng nhất định trong tất cả các trường hợp của một tên tệp cụ thể.
Ngoài ra, Set-Content
đã không trả lại kết quả nhất quán, vì vậy tôi đã phải dùng đến Out-File
.
Mã dưới đây:
$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
}
Đây là những gì làm việc tốt nhất với tôi trên phiên bản PowerShell này:
Major.Minor.Build.Revision
5.1.16299.98
Hiệu chỉnh nhỏ cho lệnh Set-Content. Nếu không tìm thấy chuỗi tìm kiếm, Set-Content
lệnh sẽ để trống (trống) tệp đích.
Trước tiên bạn có thể xác minh xem chuỗi bạn đang tìm kiếm có tồn tại hay không. Nếu không nó sẽ không thay thế bất cứ điều gì.
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"
và sau đó (get-content .\test.txt).Replace("something", "awesome") | set-content .\test.txt
sẽ không làm trống các tập tin như được đề xuất trong này.