Script do PowerShell para localizar e substituir todos os arquivos com uma extensão específica

123

Eu tenho vários arquivos de configuração no Windows Server 2008 aninhados como estes:

C:\Projects\Project_1\project1.config

C:\Projects\Project_2\project2.config

Na minha configuração, eu preciso substituir uma string como esta:

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

se tornará:

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

Pensei em usar scripts em lote, mas não havia uma boa maneira de fazer isso, e ouvi dizer que, com os scripts do PowerShell, você pode fazer isso facilmente. Encontrei exemplos de localização / substituição, mas esperava uma maneira de atravessar todas as pastas dentro do diretório C: \ Projects e encontrar quaisquer arquivos que terminassem com a extensão '.config'. Quando encontrar um, quero que ele substitua meus valores de string.

Existem bons recursos para descobrir como fazer isso ou quaisquer gurus do PowerShell que possam oferecer algumas dicas?

Brandon
fonte
1
Deixe-nos saber como você se saiu ou se houve algum problema de formatação estranho nos arquivos que precisavam ser resolvidos. Uma coisa boa sobre o problema é que ele é testado sem afetar o código de produção
Robben_Ford_Fan_boy

Respostas:

178

Aqui está uma primeira tentativa no topo da minha cabeça.

$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
fonte
3
Gostaria de acrescentar que o teste das soluções fornecidas funcionou, mas essa foi a mais fácil para facilitar a leitura. Consegui entregar isso a um colega de trabalho e ele conseguiu entender facilmente o que estava acontecendo. Obrigado pela ajuda.
Brandon
11
Para que isso funcione em arquivos em subdiretórios, você precisa ".PSPath". Curiosamente, quando tentei fazer isso funcionar sem um () em torno de obter conteúdo, ele falhou no conteúdo de gravação porque o arquivo estava bloqueado.
Frank Schwieterman
25
Versão curta (aliases comuns usados):ls *.config -rec | %{ $f=$_; (gc $f.PSPath) | %{ $_ -replace "Dev", "Demo" } | sc $f.PSPath }
Artyom
5
@ Artyom não se esqueça do .depois do ls. Eu fui picado por isso.
Pureferret 26/09/15
5
UnauthorizedAccessException também pode causar devido a pastas, se você remover o * .config para executar em todos os arquivos. Você pode adicionar filtro -File ao Get-ChildItem ... Levou um tempo para descobrir isso
Amir Katz
32

O PowerShell é uma boa opção;) É muito fácil enumerar arquivos em um determinado diretório, lê-los e processá-los.

O script pode ficar assim:

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

Dividi o código em mais linhas para ser legível para você. Observe que você pode usar Set-Content em vez de [IO.File]::WriteAllText, mas ele adiciona uma nova linha no final. Com WriteAllTextvocê pode evitá-lo.

Caso contrário, o código poderia ser assim: $c | Set-Content $_.FullName.

stej
fonte
14

Essa abordagem funciona bem:

gci C:\Projects *.config -recurse | ForEach {
  (Get-Content $_ | ForEach {$_ -replace "old", "new"}) | Set-Content $_ 
}
  • Altere "antigo" e "novo" para os valores correspondentes (ou use variáveis).
  • Não esqueça os parênteses - sem os quais você receberá um erro de acesso.
Tolga
fonte
Então, eu escolhi este aqui para uma expressão sucinta - mas tive que substituir Get-Content $_por Get-Content $_.FullName, e o equivalente Set-Content, para lidar com arquivos que não estavam na raiz.
Matt Whitfield
11

Eu iria com 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)  
}
Shay Levy
fonte
11

Este exemplo do PowerShell procura todas as instâncias da string "\ foo \" em uma pasta e suas subpastas, substitui "\ foo \" por "\ bar \" E NÃO REWRITE arquivos que não contêm a string "\ foo \ "Desta forma, você não destrói os carimbos de data / hora da última atualização do arquivo em que a cadeia não foi encontrada:

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

Eu achei útil o comentário de @Artyom, mas infelizmente ele não postou uma resposta.

Esta é a versão curta, na minha opinião, a melhor versão, da resposta aceita;

ls *.config -rec | %{$f=$_; (gc $f.PSPath) | %{$_ -replace "Dev", "Demo"} | sc $f.PSPath}
M--
fonte
1
No caso de alguém mais se deparar com isso, como eu fiz - procurando executá-lo diretamente de um arquivo em lotes - pode ser útil usar em foreach-objectvez do %alias ao executar um comando como este. Caso contrário, pode resultar no erro:Expressions are only allowed as the first element of a pipeline
Dustin Halstead
Curto nem sempre é melhor, é mais claro o que está acontecendo na versão longa.
Nick N.
@NickN. Bem, certo. Depende do seu objetivo. Eu o sinalizaria como POB;)
M-- 21/05/19
6

Eu escrevi uma pequena função auxiliar para substituir o texto em um arquivo:

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

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

Exemplo:

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

Ao fazer a substituição recursiva, o caminho e o nome do arquivo precisam ser incluídos:

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

Isso substituirá todos os "antigos" por "novos" que diferenciam maiúsculas de minúsculas em todos os arquivos das suas pastas do diretório atual.

Geir Ivar Jerstad
fonte
A parte ".PSPath" da sua resposta realmente me ajudou. Mas eu tive que mudar o "{$" interno para "$ _". Eu editar a sua resposta, mas eu não estou usando sua parte -creplace - estou usando a resposta aceita com .PSPath
aaaa bbbb