Script di PowerShell per trovare e sostituire tutti i file con un'estensione specifica


124

Ho diversi file di configurazione su Windows Server 2008 annidati in questo modo:

C:\Projects\Project_1\project1.config

C:\Projects\Project_2\project2.config

Nella mia configurazione devo fare una sostituzione di stringa in questo modo:

<add key="Environment" value="Dev"/>

diventerà:

<add key="Environment" value="Demo"/>

Ho pensato di utilizzare lo scripting batch, ma non c'era un buon modo per farlo e ho sentito che con lo scripting PowerShell puoi farlo facilmente. Ho trovato esempi di trova / sostituisci, ma speravo in un modo che attraversasse tutte le cartelle all'interno della mia directory C: \ Projects e trovasse tutti i file che terminano con l'estensione ".config". Quando ne trova uno, voglio che sostituisca i miei valori di stringa.

Qualche buona risorsa per scoprire come farlo o qualsiasi guru di PowerShell in grado di offrire alcune informazioni?


1
Facci sapere come sei andato o se ci sono stati alcuni strani problemi di formattazione con i file che dovevano essere risolti. Un aspetto positivo del problema è che è testato senza influire sul codice di produzione
Robben_Ford_Fan_boy

Risposte:


178

Ecco un primo tentativo in cima alla mia testa.

$configFiles = Get-ChildItem . *.config -rec
foreach ($file in $configFiles)
{
    (Get-Content $file.PSPath) |
    Foreach-Object { $_ -replace "Dev", "Demo" } |
    Set-Content $file.PSPath
}

3
Vorrei aggiungere che il test delle soluzioni fornite ha funzionato, ma questo è stato il più semplice per la leggibilità. Sono stato in grado di consegnarlo a un collega e lui poteva facilmente capire cosa stava succedendo. Grazie per l'assistenza.
Brandon

11
Affinché funzioni nei file nelle sottodirectory, è necessario ".PSPath". È interessante notare che quando ho provato a farlo funzionare senza un () attorno a get-content non è riuscito a scrivere contenuto perché il file era bloccato.
Frank Schwieterman

25
Versione breve (alias comuni utilizzati):ls *.config -rec | %{ $f=$_; (gc $f.PSPath) | %{ $_ -replace "Dev", "Demo" } | sc $f.PSPath }
Artyom

5
@Artyom non dimenticare il .dopo il ls. Sono stato punto da questo anch'io.
Pureferret,

5
UnauthorizedAccessException può anche causare a causa di cartelle se si rimuoverà * .config per l'esecuzione su tutti i file. Puoi aggiungere -File filter a Get-ChildItem ... Ci è voluto un po 'per capirlo
Amir Katz

32

PowerShell è una buona scelta;) È molto facile enumerare i file in una determinata directory, leggerli ed elaborarli.

Lo script potrebbe assomigliare a questo:

Get-ChildItem C:\Projects *.config -recurse |
    Foreach-Object {
        $c = ($_ | Get-Content) 
        $c = $c -replace '<add key="Environment" value="Dev"/>','<add key="Environment" value="Demo"/>'
        [IO.File]::WriteAllText($_.FullName, ($c -join "`r`n"))
    }

Ho diviso il codice in più righe per essere leggibile per te. Nota che potresti usare Set-Content invece di [IO.File]::WriteAllText, ma aggiunge una nuova riga alla fine. Con WriteAllTextpuoi evitarlo.

In caso contrario, il codice potrebbe essere la seguente: $c | Set-Content $_.FullName.


14

Questo approccio funziona bene:

gci C:\Projects *.config -recurse | ForEach {
  (Get-Content $_ | ForEach {$_ -replace "old", "new"}) | Set-Content $_ 
}
  • Cambia "vecchio" e "nuovo" con i valori corrispondenti (o usa le variabili).
  • Non dimenticare la parentesi, senza la quale riceverai un errore di accesso.

Quindi ho scelto questo per un'espressione succinta, ma ho dovuto sostituirlo Get-Content $_con Get-Content $_.FullNamee l'equivalente per Set-Contentgestire i file che non erano alla radice.
Matt Whitfield,

11

Vorrei andare con xml e xpath:

dir C:\Projects\project_*\project*.config -recurse | foreach-object{  
   $wc = [xml](Get-Content $_.fullname)
   $wc.SelectNodes("//add[@key='Environment'][@value='Dev']") | Foreach-Object {$_.value = 'Demo'}  
   $wc.Save($_.fullname)  
}

11

Questo esempio di PowerShell cerca tutte le istanze della stringa "\ foo \" in una cartella e nelle sue sottocartelle, sostituisce "\ foo \" con "\ bar \" E NON RISCRIVE i file che non contengono la stringa "\ pippo \ "In questo modo non distruggerai i timbri datetime dell'ultimo aggiornamento in cui la stringa non è stata trovata:

Get-ChildItem  -Path C:\YOUR_ROOT_PATH\*.* -recurse 
 | ForEach {If (Get-Content $_.FullName | Select-String -Pattern '\\foo\\') 
           {(Get-Content $_ | ForEach {$_ -replace '\\foo\\', '\bar\'}) | Set-Content $_ }
           }

7

Ho trovato utile il commento di @Artyom ma purtroppo non ha pubblicato una risposta.

Questa è la versione breve, secondo me la migliore versione, della risposta accettata;

ls *.config -rec | %{$f=$_; (gc $f.PSPath) | %{$_ -replace "Dev", "Demo"} | sc $f.PSPath}

1
Nel caso in cui qualcun altro si imbatta in questo, come ho fatto io, cercando di eseguirlo direttamente da un file batch, potrebbe essere utile usare al foreach-objectposto %dell'alias quando si esegue un comando come questo. In caso contrario, potrebbe verificarsi l'errore:Expressions are only allowed as the first element of a pipeline
Dustin Halstead

Il corto non è sempre migliore, è più chiaro cosa sta succedendo nella versione lunga.
Nick N.

@NickN. Bene, giusto. Dipende dal tuo obiettivo. Lo contrassegnerei come POB;)
M--

6

Ho scritto una piccola funzione di supporto per sostituire il testo in un file:

function Replace-TextInFile
{
    Param(
        [string]$FilePath,
        [string]$Pattern,
        [string]$Replacement
    )

    [System.IO.File]::WriteAllText(
        $FilePath,
        ([System.IO.File]::ReadAllText($FilePath) -replace $Pattern, $Replacement)
    )
}

Esempio:

Get-ChildItem . *.config -rec | ForEach-Object { 
    Replace-TextInFile -FilePath $_ -Pattern 'old' -Replacement 'new' 
}

4

Quando si esegue la sostituzione ricorsiva, è necessario includere il percorso e il nome del file:

Get-ChildItem -Recurse | ForEach {  (Get-Content $_.PSPath | 
ForEach {$ -creplace "old", "new"}) | Set-Content $_.PSPath }

Questo sostituirà tutto il "vecchio" con il "nuovo" con distinzione tra maiuscole e minuscole in tutti i file delle cartelle della directory corrente.


La parte ".PSPath" della tua risposta mi ha davvero aiutato. Ma ho dovuto cambiare la "{$" interna in "$ _". Modificherei la tua risposta, ma non sto usando la tua parte -creplace - Sto usando la risposta accettata con .PSPath
aaaa bbbb
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.