Script PowerShell pour rechercher et remplacer tous les fichiers avec une extension spécifique

124

J'ai plusieurs fichiers de configuration sur Windows Server 2008 imbriqués comme tels:

C:\Projects\Project_1\project1.config

C:\Projects\Project_2\project2.config

Dans ma configuration, je dois faire un remplacement de chaîne comme tel:

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

va devenir:

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

J'ai pensé à utiliser le script par lots, mais il n'y avait pas de bon moyen de le faire, et j'ai entendu dire qu'avec le script PowerShell, vous pouvez facilement le faire. J'ai trouvé des exemples de recherche / remplacement, mais j'espérais un moyen qui traverserait tous les dossiers de mon répertoire C: \ Projects et trouverait tous les fichiers qui se terminent par l'extension '.config'. Quand il en trouve un, je veux qu'il remplace mes valeurs de chaîne.

Avez-vous de bonnes ressources pour savoir comment faire cela ou des gourous de PowerShell qui peuvent offrir un aperçu?

Brandon
la source
1
Faites-nous savoir comment vous vous êtes débrouillé ou s'il y avait des problèmes de formatage étranges avec les fichiers qui devaient être résolus. Une bonne chose à propos du problème est qu'il est testé sans affecter le code de production
Robben_Ford_Fan_boy

Réponses:

178

Voici une première tentative au sommet de ma tête.

$configFiles = Get-ChildItem . *.config -rec
foreach ($file in $configFiles)
{
    (Get-Content $file.PSPath) |
    Foreach-Object { $_ -replace "Dev", "Demo" } |
    Set-Content $file.PSPath
}
Robben_Ford_Fan_boy
la source
3
Je voudrais ajouter que tester les solutions fournies fonctionnait, mais celle-ci était la plus simple pour la lisibilité. J'ai pu remettre ceci à un collègue et il pouvait facilement comprendre ce qui se passait. Merci pour l'aide.
Brandon
11
Pour que cela fonctionne dans les fichiers des sous-répertoires, vous avez besoin de ".PSPath". Fait intéressant, lorsque j'ai essayé de faire fonctionner ce travail sans () autour de get-content, cela a échoué à l'écriture du contenu car le fichier était verrouillé.
Frank Schwieterman
25
Version courte (alias courants utilisés):ls *.config -rec | %{ $f=$_; (gc $f.PSPath) | %{ $_ -replace "Dev", "Demo" } | sc $f.PSPath }
Artyom
5
@Artyom n'oublie pas l' .après ls. J'ai été piqué par ça moi-même.
Pureferret
5
UnauthorizedAccessException peut également provoquer des dossiers si vous supprimez le * .config pour qu'il s'exécute sur tous les fichiers. Vous pouvez ajouter -File filter à Get-ChildItem ... Il a fallu du temps pour le comprendre
Amir Katz
32

PowerShell est un bon choix;) Il est très facile d'énumérer les fichiers dans un répertoire donné, de les lire et de les traiter.

Le script pourrait ressembler à ceci:

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"))
    }

J'ai divisé le code en plusieurs lignes pour qu'il soit lisible pour vous. Notez que vous pouvez utiliser Set-Content à la place de [IO.File]::WriteAllText, mais cela ajoute une nouvelle ligne à la fin. Avec WriteAllTextvous pouvez l'éviter.

Sinon , le code pourrait ressembler à ceci: $c | Set-Content $_.FullName.

stej
la source
14

Cette approche fonctionne bien:

gci C:\Projects *.config -recurse | ForEach {
  (Get-Content $_ | ForEach {$_ -replace "old", "new"}) | Set-Content $_ 
}
  • Remplacez «ancien» et «nouveau» par leurs valeurs correspondantes (ou utilisez des variables).
  • N'oubliez pas la parenthèse - sans laquelle vous recevrez une erreur d'accès.
Tolga
la source
Je suis donc allé avec celui-ci pour une expression succincte - mais j'ai dû le remplacer Get-Content $_par Get-Content $_.FullName, et l'équivalent Set-Contentpour qu'il gère les fichiers qui n'étaient pas à la racine.
Matt Whitfield
11

J'irais avec xml et 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)  
}
Shay Levy
la source
11

Cet exemple PowerShell recherche toutes les instances de la chaîne "\ foo \" dans un dossier et ses sous-dossiers, remplace "\ foo \" par "\ bar \" ET NE RÉÉCRIVE PAS les fichiers qui ne contiennent pas la chaîne "\ foo \ "De cette façon, vous ne détruisez pas les horodatages de la dernière mise à jour du fichier où la chaîne n'a pas été trouvée:

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 $_ }
           }
Kaye
la source
7

J'ai trouvé le commentaire de @Artyom utile mais malheureusement il n'a pas posté de réponse.

C'est la version courte, à mon avis la meilleure version, de la réponse acceptée;

ls *.config -rec | %{$f=$_; (gc $f.PSPath) | %{$_ -replace "Dev", "Demo"} | sc $f.PSPath}
M--
la source
1
Au cas où quelqu'un d'autre rencontrerait cela, comme je l'ai fait - cherchant à l'exécuter directement à partir d'un fichier batch - il peut être utile d'utiliser à la foreach-objectplace de l' %alias lors de l'exécution d'une commande comme celle-ci. Sinon, cela peut entraîner l'erreur:Expressions are only allowed as the first element of a pipeline
Dustin Halstead
Le court n'est pas toujours mieux, c'est plus clair ce qui se passe dans la version longue.
Nick N.21
@NickN. Eh bien, c'est vrai. Cela dépend de votre objectif. Je le marquerais comme POB;)
M--
6

J'ai écrit une petite fonction d'aide pour remplacer du texte dans un fichier:

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

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

Exemple:

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

Lors d'un remplacement récursif, le chemin et le nom de fichier doivent être inclus:

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

Cela remplacera tous les "anciens" par "nouveaux" sensibles à la casse dans tous les fichiers de vos dossiers de votre répertoire actuel.

Geir Ivar Jerstad
la source
La partie ".PSPath" de votre réponse m'a vraiment aidé. Mais j'ai dû changer le "{$" intérieur en "$ _". Je modifierais votre réponse, mais je n'utilise pas votre partie de remplacement
J'utilise