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?
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?
Réponses:
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
(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ù ...
Set-Content
au lieu de Out-File
yuou 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." .
Get-Content
parenthèses, cela fonctionne. Pouvez-vous expliquer dans votre réponse pourquoi la parenthèse est nécessaire? Je remplacerais encore Out-File
avec Set-Content
parce qu'il est plus sûr; il vous protège contre l'effacement du fichier cible si vous oubliez la parenthèse.
Set-Content
au lieu de Out-File
est une solution bien meilleure et plus sûre. Désolé d'avoir à voter.
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.
[System.IO.File] | gm
C:\Windows\System32\WindowsPowerShell\v1.0
?
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 $_
}
foreach
vous pouvez le faireGet-ChildItem 'C:\folder\file*.xml' -Recurse | ForEach { (Get-Content $_).Replace('[MYID]', 'MyValue') | Set-Content $_ }
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 }
où $Directory
est le répertoire contenant les fichiers que vous souhaitez modifier.
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.
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'
$myFile
?
$a = 'file.txt'; invoke-expression "`${c:$a} = `${c:$a} -replace 'oldvalue','newvalue'"
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
#>
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
Cela a fonctionné pour moi en utilisant le répertoire de travail actuel dans PowerShell. Vous devez utiliser la FullName
propriété, sinon elle ne fonctionnera pas dans PowerShell version 5. J'avais besoin de modifier la version cible du framework .NET dans TOUS mes CSPROJ
fichiers.
gci -Recurse -Filter *.csproj |
% { (get-content "$($_.FullName)")
.Replace('<TargetFramework>net47</TargetFramework>', '<TargetFramework>net462</TargetFramework>') |
Set-Content "$($_.FullName)"}
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-Content
ne 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
Petite correction pour la commande Set-Content. Si la chaîne recherchée n'est pas trouvée, la Set-Content
commande 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"}
set-content test.txt "hello hello world hello world hello"
, puis (get-content .\test.txt).Replace("something", "awesome") | set-content .\test.txt
ne videra pas le fichier comme suggéré dans ceci.