Utilizzando PowerShell, voglio sostituire tutte le occorrenze esatte di [MYID]
in un determinato file con MyValue
. Qual è il modo più semplice per farlo?
Utilizzando PowerShell, voglio sostituire tutte le occorrenze esatte di [MYID]
in un determinato file con MyValue
. Qual è il modo più semplice per farlo?
Risposte:
Usa (versione V3):
(Get-Content c:\temp\test.txt).replace('[MYID]', 'MyValue') | Set-Content c:\temp\test.txt
O per 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
Nota (Get-Content file.txt)
: sono necessarie le parentesi :
Senza la parentesi il contenuto viene letto, una riga alla volta, e scorre lungo la pipeline fino a quando non raggiunge out-file o set-content, che tenta di scrivere nello stesso file, ma è già aperto da get-content e ottieni un errore. La parentesi fa sì che l'operazione di lettura del contenuto venga eseguita una volta (apri, leggi e chiudi). Solo allora quando tutte le righe sono state lette, vengono reindirizzate una alla volta e quando raggiungono l'ultimo comando nella pipeline possono essere scritte nel file. È lo stesso di $ content = content; $ content | dove ...
Set-Content
invece di Out-File
ottenere un avviso del tipo "Il processo non può accedere al file '123.csv' perché viene utilizzato da un altro processo." .
Get-Content
tra parentesi funziona. Puoi spiegare nella tua risposta perché è necessaria la parentesi? Vorrei ancora sostituirlo Out-File
con Set-Content
perché è più sicuro; ti protegge dal cancellare il file di destinazione se dimentichi la parentesi.
Set-Content
invece di Out-File
è una soluzione molto migliore e più sicura. Mi dispiace dover votare.
Preferisco usare la classe File di .NET e i suoi metodi statici come mostrato nell'esempio seguente.
$content = [System.IO.File]::ReadAllText("c:\bla.txt").Replace("[MYID]","MyValue")
[System.IO.File]::WriteAllText("c:\bla.txt", $content)
Questo ha il vantaggio di lavorare con una singola stringa anziché con un array di stringhe come con Get-Content . I metodi si occupano anche della codifica del file (UTF-8 BOM , ecc.) Senza che tu debba occupartene per la maggior parte del tempo.
Inoltre, i metodi non confondono le terminazioni di linea (terminazioni di linea Unix che potrebbero essere utilizzate) in contrasto con un algoritmo che utilizza Get-Content e il collegamento a Set-Content .
Quindi per me: meno cose che potrebbero rompersi nel corso degli anni.
Una cosa poco nota quando si usano le classi .NET è che quando si è digitato "[System.IO.File] ::" nella finestra di PowerShell è possibile premere il Tabtasto per scorrere i metodi lì.
[System.IO.File] | gm
C:\Windows\System32\WindowsPowerShell\v1.0
?
Quello sopra funziona solo per "Un file", ma puoi anche eseguirlo per più file nella tua cartella:
Get-ChildItem 'C:yourfile*.xml' -Recurse | ForEach {
(Get-Content $_ | ForEach { $_ -replace '[MYID]', 'MyValue' }) |
Set-Content $_
}
foreach
puoi farloGet-ChildItem 'C:\folder\file*.xml' -Recurse | ForEach { (Get-Content $_).Replace('[MYID]', 'MyValue') | Set-Content $_ }
foreach
, perché Get-Content fa qualcosa che potresti non aspettarti ... Restituisce una matrice di stringhe, dove ogni stringa è una riga nel file. Se stai eseguendo il ciclo continuo di una directory (e delle sottodirectory) che si trovano in una posizione diversa rispetto allo script in esecuzione, ti consigliamo qualcosa del genere: Get-ChildItem $Directory -File -Recurse | ForEach { (Get-Content $_.FullName) | ForEach { $_ -replace '[MYID]', 'MyValue' } | Set-Content $_.FullName }
dov'è $Directory
la directory che contiene i file che vuoi modificare.
Questo è quello che uso, ma è lento su file di testo di grandi dimensioni.
get-content $pathToFile | % { $_ -replace $stringToReplace, $replaceWith } | set-content $pathToFile
Se stai per sostituire stringhe in file di testo di grandi dimensioni e la velocità è un problema, cerca di utilizzare System.IO.StreamReader e 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()
}
}
(Il codice sopra non è stato testato.)
C'è probabilmente un modo più elegante di usare StreamReader e StreamWriter per sostituire il testo in un documento, ma ciò dovrebbe darti un buon punto di partenza.
Ho trovato un modo poco conosciuto ma incredibilmente bello per farlo dal Windows Powershell di Payette in azione . Puoi fare riferimento a file come variabili, simili a $ env: path, ma devi aggiungere le parentesi graffe.
${c:file.txt} = ${c:file.txt} -replace 'oldvalue','newvalue'
$myFile
?
$a = 'file.txt'; invoke-expression "`${c:$a} = `${c:$a} -replace 'oldvalue','newvalue'"
Se è necessario sostituire le stringhe in più file:
Va notato che i diversi metodi pubblicati qui possono essere molto diversi per quanto riguarda il tempo necessario per il completamento. Per me, ho regolarmente un gran numero di piccoli file. Per testare ciò che è più performante, ho estratto 5,52 GB (5.933.604.999 byte) di XML in 40.693 file separati e ho analizzato tre delle risposte che ho trovato qui:
## 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
#>
Ringraziamo @ rominator007
L'ho avvolto in una funzione (perché potresti voler usarlo di nuovo)
function Replace-AllStringsInFile($SearchString,$ReplaceString,$FullPathToFile)
{
$content = [System.IO.File]::ReadAllText("$FullPathToFile").Replace("$SearchString","$ReplaceString")
[System.IO.File]::WriteAllText("$FullPathToFile", $content)
}
NOTA: questo NON distingue tra maiuscole e minuscole !!!!!
Vedi questo messaggio: String.Replace ignorating case
Questo ha funzionato per me usando l'attuale directory di lavoro in PowerShell. È necessario utilizzare la FullName
proprietà o non funzionerà in PowerShell versione 5. Ho dovuto modificare la versione di .NET framework di destinazione in TUTTI i miei CSPROJ
file.
gci -Recurse -Filter *.csproj |
% { (get-content "$($_.FullName)")
.Replace('<TargetFramework>net47</TargetFramework>', '<TargetFramework>net462</TargetFramework>') |
Set-Content "$($_.FullName)"}
Un po 'vecchio e diverso, in quanto avevo bisogno di cambiare una determinata riga in tutte le istanze di un nome file specifico.
Inoltre, Set-Content
non stava restituendo risultati coerenti, quindi ho dovuto ricorrere a Out-File
.
Codice sotto:
$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
}
Questo è ciò che ha funzionato meglio per me in questa versione di PowerShell:
Major.Minor.Build.Revision
5.1.16299.98
Piccola correzione per il comando Set-Content. Se la stringa cercata non viene trovata, il Set-Content
comando cancella (svuota) il file di destinazione.
Puoi prima verificare se la stringa che stai cercando esiste o no. Altrimenti non sostituirà nulla.
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"
quindi (get-content .\test.txt).Replace("something", "awesome") | set-content .\test.txt
non svuoterà il file come suggerito in questo.