Comment puis-je remplacer chaque occurrence d'une chaîne dans un fichier par PowerShell?

289

En utilisant PowerShell, je veux remplacer toutes les occurrences exactes de [MYID]dans un fichier donné par MyValue. Quelle est la manière la plus simple de procéder?

amateur
la source
Pour une solution plus efficace en termes de consommation de mémoire que celle proposée dans les réponses à cette question, voir Rechercher et remplacer dans un fichier volumineux .
Martin Prikryl

Réponses:

444

Utilisation (version V3):

(Get-Content c:\temp\test.txt).replace('[MYID]', 'MyValue') | Set-Content c:\temp\test.txt

Ou pour V2:

(Get-Content c:\temp\test.txt) -replace '\[MYID\]', 'MyValue' | Set-Content c:\temp\test.txt
Loïc MICHEL
la source
3
Merci - j'obtiens une erreur "replace: l'invocation de la méthode a échoué car [System.Object []] ne contient pas de méthode nommée 'replace'." bien que?
amateur
\ comme l'évasion fonctionne dans ps v4 que je viens de découvrir. Merci.
ErikE
4
@rob canalisez le résultat vers set-content ou out-file si vous voulez enregistrer la modification
Loïc MICHEL
2
J'ai reçu l'erreur "L'invocation de la méthode a échoué car [System.Object []] ne contient pas de méthode nommée 'replace'." parce que j'essayais d'exécuter la version V3 sur une machine qui n'avait que V2.
SFlagg
5
Avertissement: l'exécution de ces scripts sur des fichiers volumineux (quelques centaines de mégaoctets environ) peut consommer une bonne quantité de mémoire. Assurez-vous simplement que vous disposez de suffisamment d'espace pour la tête si vous utilisez un serveur de production: D
neoscribe
89
(Get-Content file.txt) | 
Foreach-Object {$_ -replace '\[MYID\]','MyValue'}  | 
Out-File file.txt

Notez que les parenthèses autour (Get-Content file.txt)sont obligatoires:

Sans les parenthèses, le contenu est lu, une ligne à la fois, et descend le pipeline jusqu'à ce qu'il atteigne le fichier sortant ou le contenu défini, qui essaie d'écrire dans le même fichier, mais il est déjà ouvert par get-content et vous obtenez une erreur. La parenthèse fait que l'opération de lecture de contenu est effectuée une fois (ouverture, lecture et fermeture). Ce n'est qu'à ce moment que toutes les lignes ont été lues, elles sont redirigées une par une et lorsqu'elles atteignent la dernière commande du pipeline, elles peuvent être écrites dans le fichier. C'est la même chose que $ content = content; $ content | où ...

Shay Levy
la source
5
Je changerais mon upvote en downvote si je le pouvais. Dans PowerShell 3, cela supprime silencieusement tout le contenu du fichier! En utilisant Set-Contentau lieu de Out-Fileyuou vous obtenez un avertissement du type "Le processus ne peut pas accéder au fichier '123.csv' car il est utilisé par un autre processus." .
Iain Samuel McLean Elder
9
Cela ne devrait pas se produire lorsque le contenu get est entre parenthèses. Ils provoquent l'ouverture, la lecture et la fermeture du fichier afin que l'erreur que vous obtenez ne se produise pas. Pouvez-vous le tester à nouveau avec un exemple de fichier texte?
Shay Levy
2
Entre Get-Contentparenthèses, cela fonctionne. Pouvez-vous expliquer dans votre réponse pourquoi la parenthèse est nécessaire? Je remplacerais encore Out-Fileavec Set-Contentparce qu'il est plus sûr; il vous protège contre l'effacement du fichier cible si vous oubliez la parenthèse.
Iain Samuel McLean Elder
6
Problème avec l' encodage de fichier UTF-8 . Lorsque enregistre le fichier, modifie l'encodage. Pas le même. stackoverflow.com/questions/5596982/… . Je pense que set-content considère le fichier d'encodage (comme UTF-8). mais pas Out-File
Kiquenet
1
Cette solution est indûment trompeuse et a causé des problèmes lorsque je l'ai utilisée. Je mettais à jour un fichier de configuration qui a été immédiatement utilisé par le processus d'installation. Le fichier de configuration était toujours conservé par le processus et l'installation a échoué. Utiliser Set-Contentau lieu de Out-Fileest une solution bien meilleure et plus sûre. Désolé d'avoir à voter.
Martin Basista
81

Je préfère utiliser la classe File de .NET et ses méthodes statiques comme le montre l'exemple suivant.

$content = [System.IO.File]::ReadAllText("c:\bla.txt").Replace("[MYID]","MyValue")
[System.IO.File]::WriteAllText("c:\bla.txt", $content)

Cela a l'avantage de travailler avec une seule chaîne au lieu d'un tableau de chaînes comme avec Get-Content . Les méthodes prennent également en charge l'encodage du fichier ( BOM UTF-8 , etc.) sans que vous ayez à vous en préoccuper la plupart du temps.

De plus, les méthodes ne gâchent pas les fins de ligne (fins de ligne Unix qui pourraient être utilisées) contrairement à un algorithme utilisant Get-Content et passant par Set-Content .

Donc pour moi: moins de choses qui pourraient se briser au fil des ans.

Une chose peu connue lors de l'utilisation de classes .NET est que lorsque vous avez tapé "[System.IO.File] ::" dans la fenêtre PowerShell, vous pouvez appuyer sur la Tabtouche pour parcourir les méthodes.

rominator007
la source
Vous pouvez également voir les méthodes avec la commande [System.IO.File] | gm
fbehrens
Pourquoi cette méthode suppose-t-elle un chemin relatif à partir de C:\Windows\System32\WindowsPowerShell\v1.0?
Adrian
Est-ce vrai? Cela a probablement quelque chose à voir avec la façon dont un domaine d'application .NET est démarré dans PowerShell. Il se peut que le chemin actuel ne soit pas mis à jour lors de l'utilisation de cd. Mais ce n'est qu'une supposition éclairée. Je n'ai pas testé cela ni recherché.
rominator007
2
C'est aussi beaucoup plus facile que d'écrire du code différent pour différentes versions de Powershell.
Willem van Ketwich
Cette méthode semble également être la plus rapide. Ajoutez à cela les avantages notés et la question devrait être: "pourquoi voudriez-vous utiliser autre chose?"
DBADon
21

Celui ci-dessus ne fonctionne que pour "One File" uniquement, mais vous pouvez également l'exécuter pour plusieurs fichiers dans votre dossier:

Get-ChildItem 'C:yourfile*.xml' -Recurse | ForEach {
     (Get-Content $_ | ForEach  { $_ -replace '[MYID]', 'MyValue' }) |
     Set-Content $_
}
John V Hobbs Jr
la source
remarquez que j'ai utilisé .xml mais vous pouvez le remplacer par .txt
John V Hobbs Jr
Agréable. Alternativement à l'utilisation de l'intérieur, foreachvous pouvez le faireGet-ChildItem 'C:\folder\file*.xml' -Recurse | ForEach { (Get-Content $_).Replace('[MYID]', 'MyValue') | Set-Content $_ }
KCD
1
En fait, vous avez besoin de cet intérieur foreach, car Get-Content fait quelque chose que vous ne pourriez pas attendre ... Il renvoie un tableau de chaînes, où chaque chaîne est une ligne dans le fichier. Si vous parcourez un répertoire (et des sous-répertoires) qui se trouvent dans un emplacement différent de votre script en cours d'exécution, vous voudrez quelque chose comme ceci: Get-ChildItem $Directory -File -Recurse | ForEach { (Get-Content $_.FullName) | ForEach { $_ -replace '[MYID]', 'MyValue' } | Set-Content $_.FullName }$Directoryest le répertoire contenant les fichiers que vous souhaitez modifier.
birdamongmen
1
Quelle réponse est "celle ci-dessus"?
Peter Mortensen
10

Vous pouvez essayer quelque chose comme ça:

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

C'est ce que j'utilise, mais c'est lent sur les gros fichiers texte.

get-content $pathToFile | % { $_ -replace $stringToReplace, $replaceWith } | set-content $pathToFile

Si vous allez remplacer des chaînes dans des fichiers texte volumineux et que la vitesse est un problème, essayez d' utiliser System.IO.StreamReader et 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()
   }
}

(Le code ci-dessus n'a pas été testé.)

Il existe probablement une manière plus élégante d'utiliser StreamReader et StreamWriter pour remplacer du texte dans un document, mais cela devrait vous donner un bon point de départ.

Darrell Lloyd Harvey
la source
Je pense que set-content considère le fichier d'encodage (comme UTF-8). mais pas Out-File stackoverflow.com/questions/5596982/…
Kiquenet
2

J'ai trouvé un moyen peu connu mais incroyablement cool de le faire à partir de Windows Powershell en action de Payette . Vous pouvez référencer des fichiers comme des variables, similaires à $ env: path, mais vous devez ajouter les accolades.

${c:file.txt} = ${c:file.txt} -replace 'oldvalue','newvalue'
js2010
la source
Que faire si le nom de fichier se trouve dans une variable telle que $myFile?
ΩmegaMan
@ ΩmegaMan hmm seulement jusqu'à présent$a = 'file.txt'; invoke-expression "`${c:$a} = `${c:$a} -replace 'oldvalue','newvalue'"
js2010
2

Si vous devez remplacer des chaînes dans plusieurs fichiers:

Il convient de noter que les différentes méthodes affichées ici peuvent être très différentes en ce qui concerne le temps qu'il faut pour terminer. Pour moi, j'ai régulièrement un grand nombre de petits fichiers. Pour tester ce qui est le plus performant, j'ai extrait 5,52 Go (5 933 604 999 octets) de XML dans 40 693 fichiers séparés et parcouru trois des réponses que j'ai trouvées ici:

## 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
#>
DBADon
la source
La question portait sur le remplacement des chaînes dans un fichier donné, et non sur plusieurs fichiers.
PL
1

Crédit à @ rominator007

Je l'ai enveloppé dans une fonction (car vous voudrez peut-être l'utiliser à nouveau)

function Replace-AllStringsInFile($SearchString,$ReplaceString,$FullPathToFile)
{
    $content = [System.IO.File]::ReadAllText("$FullPathToFile").Replace("$SearchString","$ReplaceString")
    [System.IO.File]::WriteAllText("$FullPathToFile", $content)
}

REMARQUE: ce n'est pas sensible à la casse !!!!!

Voir cet article: String.Replace ignoring case

Kolob Canyon
la source
0

Cela a fonctionné pour moi en utilisant le répertoire de travail actuel dans PowerShell. Vous devez utiliser la FullNamepropriété, sinon elle ne fonctionnera pas dans PowerShell version 5. J'avais besoin de modifier la version cible du framework .NET dans TOUS mes CSPROJfichiers.

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

Un peu vieux et différent, car j'avais besoin de changer une certaine ligne dans toutes les instances d'un nom de fichier particulier.

De plus, Set-Contentne retournait pas de résultats cohérents, j'ai donc dû y recourir Out-File.

Code ci-dessous:


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

C'est ce qui a le mieux fonctionné pour moi sur cette version PowerShell:

Major.Minor.Build.Revision

5.1.16299.98

Kalin Tashev
la source
-1

Petite correction pour la commande Set-Content. Si la chaîne recherchée n'est pas trouvée, la Set-Contentcommande vide (vide) le fichier cible.

Vous pouvez d'abord vérifier si la chaîne que vous recherchez existe ou non. Sinon, il ne remplacera rien.

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"}
dan D
la source
3
Bienvenue sur StackOverflow! Veuillez utiliser le formatage, vous pouvez lire cet article si vous avez besoin d'aide.
CodenameLambda
1
Ce n'est pas vrai, si l'on utilise la bonne réponse et que le remplacement n'est pas trouvé, il écrit toujours le fichier, mais il n'y a aucun changement. Par exemple set-content test.txt "hello hello world hello world hello", puis (get-content .\test.txt).Replace("something", "awesome") | set-content .\test.txtne videra pas le fichier comme suggéré dans ceci.
Ciantic