Làm cách nào tôi có thể thay thế mọi lần xuất hiện của Chuỗi trong tệp bằng PowerShell?


289

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ó giải pháp hiệu quả hơn về mặt tiêu thụ bộ nhớ so với câu trả lời cho câu hỏi này, hãy xem Tìm và thay thế trong một tệp lớn .
Martin Prikryl

Câu trả lời:


444

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

3
Cảm ơn - Tôi gặp lỗi "thay thế: Gọi phương thức không thành công vì [System.Object []] không chứa phương thức có tên 'thay thế'." Tuy nhiên?
nghiệp dư

\ như lối thoát hoạt động trong ps v4 tôi mới phát hiện ra. Cảm ơn.
ErikE

4
@rob chuyển kết quả thành tập tin nội dung hoặc tập tin bên ngoài nếu bạn muốn lưu sửa đổi
Loïc MICHEL

2
Tôi đã gặp lỗi "Gọi phương thức không thành công vì [System.Object []] không chứa phương thức có tên 'thay thế'." bởi vì tôi đã cố chạy phiên bản V3 trên một máy chỉ có V2.
SFlagg

5
Cảnh báo: Chạy các tập lệnh này với các tệp lớn (vài trăm megabyte hoặc hơn) có thể chiếm một lượng bộ nhớ khá lớn. Chỉ cần chắc chắn rằng bạn có đủ phòng đầu nếu bạn đang chạy trên một máy chủ sản xuất: D
neoscribe

89
(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 ...


5
Tôi sẽ thay đổi upvote của tôi thành downvote nếu tôi có thể. Trong PowerShell 3, điều này sẽ âm thầm xóa tất cả nội dung khỏi tệp! Sử dụng Set-Contentthay vì Out-Fileyuou 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." .
Iain Samuel McLean Elder

9
Nó không nên xảy ra khi nội dung get nằm trong ngoặc đơn. Chúng khiến thao tác mở, đọc và đóng tệp để xảy ra lỗi bạn không nên xảy ra. Bạn có thể kiểm tra lại với một tệp văn bản mẫu không?
Shay Levy

2
Với Get-Contenttrong 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-Filevới Set-Contentvì 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.
Iain Samuel McLean Elder

6
Sự cố với mã hóa tệp UTF-8 . Khi lưu tệp, thay đổi mã hóa. Không giống nhau. stackoverflow.com/questions/5596982/ cường . Tôi nghĩ rằng nội dung tập hợp xem xét tệp Mã hóa (như UTF-8). nhưng không phải là Out-File
Kiquenet

1
Giải pháp này gây hiểu lầm không đáng có và gây ra vấn đề khi tôi sử dụng nó. Tôi đã cập nhật một tập tin cấu hình được sử dụng ngay lập tức bởi quá trình cài đặt. Các tập tin cấu hình vẫn được giữ theo quy trình và cài đặt thất bại. Sử dụng Set-Contentthay vì Out-Filelà giải pháp tốt hơn và an toàn hơn nhiều. Xin lỗi phải downvote.
Martin Basista

81

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 ở đó.


Bạn cũng có thể xem các phương thức bằng lệnh [System.IO.File] | gm
fbehren

Tại sao phương pháp này giả định một đường dẫn tương đối từ C:\Windows\System32\WindowsPowerShell\v1.0?
Adrian

Là vậy sao? Điều đó có thể có liên quan đến cách khởi động .NET AppDomain trong PowerShell. Có thể, đường dẫn hiện tại không được cập nhật khi sử dụng cd. Nhưng điều này không hơn một phỏng đoán có giáo dục. Tôi đã không kiểm tra điều này hoặc tìm kiếm nó.
rominator007 ngày

2
Điều này cũng dễ dàng hơn nhiều so với việc viết mã khác nhau cho các phiên bản khác nhau của Powershell.
Willem van Ketwich

Phương pháp này cũng có vẻ là nhanh nhất. Cặp đôi với những lợi ích đã được ghi nhận và câu hỏi nên là, "tại sao bạn lại muốn sử dụng bất cứ thứ gì khác?"
DBADon

21

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 $_
}

lưu ý rằng tôi đã sử dụng .xml nhưng bạn có thể thay thế bằng .txt
John V Hobbs Jr

Đẹp. Ngoài ra, để sử dụng phần bên trong, foreachbạ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 $_ }
KCD

1
Trên thực tế, bạn cần nội dung đó 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 $Directorylà thư mục chứa các tệp bạn muốn sửa đổi.
Birdamongmen

1
Câu trả lời nào là "cái ở trên"?
Peter Mortensen

10

Bạn có thể thử một cái gì đó như thế này:

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

7

Đâ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.StreamReaderSystem.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 nghĩ rằng nội dung tập hợp xem xét tệp Mã hóa (như UTF-8). nhưng không phải là Out-File stackoverflow.com/questions/5596982/ từ
Kiquenet

2

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'

Điều gì xảy ra nếu tên tệp nằm trong một biến như $myFile?
ΩmegaMan

@ ΩmegaMan hmm chỉ điều này cho đến nay$a = 'file.txt'; invoke-expression "`${c:$a} = `${c:$a} -replace 'oldvalue','newvalue'"
js2010

2

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
#>

Câu hỏi là về việc thay thế các chuỗi trong một tệp nhất định, không phải nhiều tệp.
PL

1

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


0

Đ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 FullNametí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Ả CSPROJcác tệp của mình .

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

0

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


-1

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-Contentlệ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"}

3
Chào mừng bạn đến với StackOverflow! Vui lòng sử dụng định dạng, bạn có thể đọc bài viết này nếu bạn cần giúp đỡ.
CodenameLambda

1
Điều này không đúng, nếu một người sử dụng câu trả lời đúng và không tìm thấy sự thay thế, nó vẫn ghi tệp, nhưng không có thay đổi. Ví dụ set-content test.txt "hello hello world hello world hello"và sau đó (get-content .\test.txt).Replace("something", "awesome") | set-content .\test.txtsẽ không làm trống các tập tin như được đề xuất trong này.
Ciantic
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.