Come posso sostituire ogni occorrenza di una stringa in un file con PowerShell?


Risposte:


444

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

3
Grazie - Ricevo un errore "Sostituisci: invocazione del metodo non riuscita perché [System.Object []] non contiene un metodo chiamato" sostituisci "." anche se?
dilettante

\ as escape funziona in ps v4 che ho appena scoperto. Grazie.
ErikE

4
@rob reindirizza il risultato su set-content o out-file se si desidera salvare la modifica
Loïc MICHEL,

2
Ho ricevuto l'errore "Invocazione del metodo non riuscita perché [System.Object []] non contiene un metodo denominato" sostituisci "." perché stavo cercando di eseguire la versione V3 su una macchina che aveva solo V2.
SFlagg

5
Avvertenza: l'esecuzione di questi script su file di grandi dimensioni (circa duecento megabyte) può consumare una buona quantità di memoria. Assicurati di avere abbastanza spazio per la testa se
corri

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


5
Se potessi, cambierei il mio voto in un voto negativo. In PowerShell 3 questo elimina silenziosamente tutto il contenuto dal file! Utilizzando Set-Contentinvece di Out-Fileottenere un avviso del tipo "Il processo non può accedere al file '123.csv' perché viene utilizzato da un altro processo." .
Iain Samuel McLean Elder,

9
Non dovrebbe succedere quando get-content è tra parentesi. Fanno sì che l'operazione si apra, legga e chiuda il file in modo che l'errore che si ottiene non dovrebbe accadere. Puoi testarlo di nuovo con un file di testo di esempio?
Shay Levy,

2
Con Get-Contenttra parentesi funziona. Puoi spiegare nella tua risposta perché è necessaria la parentesi? Vorrei ancora sostituirlo Out-Filecon Set-Contentperché è più sicuro; ti protegge dal cancellare il file di destinazione se dimentichi la parentesi.
Iain Samuel McLean Elder,

6
Problema con la codifica dei file UTF-8 . Quando salva il file, cambia la codifica. Non lo stesso. stackoverflow.com/questions/5596982/… . Penso che set-content consideri il file di codifica (come UTF-8). ma non Out-File
Kiquenet,

1
Questa soluzione è inutilmente fuorviante e ha causato problemi quando l'ho usata. Stavo aggiornando un file di configurazione che è stato immediatamente utilizzato dal processo di installazione. Il file di configurazione era ancora trattenuto dal processo e l'installazione non è riuscita. Usare Set-Contentinvece di Out-Fileè una soluzione molto migliore e più sicura. Mi dispiace dover votare.
Martin Basista,

81

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ì.


Puoi anche vedere i metodi con il comando [System.IO.File] | gm
fbehrens,

Perché questo metodo assume un percorso relativo da C:\Windows\System32\WindowsPowerShell\v1.0?
Adrian

È così? Ciò ha probabilmente a che fare con il modo in cui un AppDomain .NET viene avviato in PowerShell. Potrebbe essere che il percorso corrente non venga aggiornato quando si utilizza cd. Ma questo non è altro che un'ipotesi colta. Non l'ho provato o cercato.
rominator007

2
Questo è anche molto più semplice della scrittura di codice diverso per le diverse versioni di Powershell.
Willem van Ketwich,

Questo metodo sembra anche essere il più veloce. Abbinalo ai vantaggi noti e alla domanda dovrebbe essere "perché dovresti voler usare qualcos'altro?"
DBADon

21

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

nota che ho usato .xml ma puoi sostituirlo con .txt
John V Hobbs Jr

Bello. In alternativa all'uso dell'interno foreachpuoi farloGet-ChildItem 'C:\folder\file*.xml' -Recurse | ForEach { (Get-Content $_).Replace('[MYID]', 'MyValue') | Set-Content $_ }
KCD il

1
In realtà, hai bisogno di quello interiore 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'è $Directoryla directory che contiene i file che vuoi modificare.
Birdamongmen,

1
Quale risposta è "quella sopra"?
Peter Mortensen,

10

Potresti provare qualcosa del genere:

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

7

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.


Penso che set-content consideri il file di codifica (come UTF-8). ma non Out-File stackoverflow.com/questions/5596982/...
Kiquenet

2

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'

Cosa succede se il nome file si trova in una variabile come $myFile?
ΩmegaMan,

@ ΩmegaMan hmm solo questo finora$a = 'file.txt'; invoke-expression "`${c:$a} = `${c:$a} -replace 'oldvalue','newvalue'"
js2010

2

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

La domanda riguardava la sostituzione di stringhe in un determinato file, non più file.
PL,

1

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


0

Questo ha funzionato per me usando l'attuale directory di lavoro in PowerShell. È necessario utilizzare la FullNameproprietà o non funzionerà in PowerShell versione 5. Ho dovuto modificare la versione di .NET framework di destinazione in TUTTI i miei CSPROJfile.

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

0

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


-1

Piccola correzione per il comando Set-Content. Se la stringa cercata non viene trovata, il Set-Contentcomando 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"}

3
Benvenuto in StackOverflow! Utilizza la formattazione, puoi leggere questo articolo se hai bisogno di aiuto.
Nome in codice

1
Questo non è vero, se si utilizza la risposta corretta e la sostituzione non viene trovata, scrive ancora il file, ma non ci sono modifiche. Ad esempio, set-content test.txt "hello hello world hello world hello"quindi (get-content .\test.txt).Replace("something", "awesome") | set-content .\test.txtnon svuoterà il file come suggerito in questo.
Ciantic,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.