Skrypt programu PowerShell do znajdowania i zastępowania wszystkich plików z określonym rozszerzeniem

124

Mam kilka plików konfiguracyjnych w systemie Windows Server 2008 zagnieżdżonych w następujący sposób:

C:\Projects\Project_1\project1.config

C:\Projects\Project_2\project2.config

W mojej konfiguracji muszę wykonać zamianę stringów w następujący sposób:

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

stanie się:

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

Myślałem o użyciu skryptów wsadowych, ale nie było na to dobrego sposobu i słyszałem, że dzięki skryptom PowerShell można to łatwo wykonać. Znalazłem przykłady funkcji znajdź / zamień, ale liczyłem na sposób, aby przeszukać wszystkie foldery w moim katalogu C: \ Projects i znaleźć wszystkie pliki, które kończą się rozszerzeniem „.config”. Kiedy znajdzie jeden, chcę, aby zastąpił moje wartości ciągu.

Jakieś dobre zasoby, aby dowiedzieć się, jak to zrobić, lub jakikolwiek guru PowerShell, który może zaoferować wgląd?

Brandon
źródło
1
Daj nam znać, jak sobie radzisz lub czy były jakieś dziwne problemy z formatowaniem plików, które wymagały rozwiązania. Jedną dobrą rzeczą w tym problemie jest to, że jest testowany bez wpływu na kod produkcyjny
Robben_Ford_Fan_boy

Odpowiedzi:

178

Tutaj pierwsza próba na czubku głowy.

$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
źródło
3
Dodam, że testowanie dostarczonych rozwiązań zadziałało, ale ten był najłatwiejszy do odczytania. Udało mi się to przekazać współpracownikowi, a on mógł łatwo zrozumieć, co się dzieje. Dzięki za pomoc.
Brandon
11
Aby to działało w plikach w podkatalogach, potrzebujesz ".PSPath". Co ciekawe, kiedy próbowałem sprawić, by ta działała bez () wokół get-content, nie udało się to przy write-content, ponieważ plik był zablokowany.
Frank Schwieterman
25
Wersja skrócona (używane wspólne aliasy):ls *.config -rec | %{ $f=$_; (gc $f.PSPath) | %{ $_ -replace "Dev", "Demo" } | sc $f.PSPath }
Artem
5
@Artyom nie zapomnij o .po ls. Sam zostałem przez to ukąszony.
Pureferret
5
UnauthorizedAccessException może również powodować foldery, jeśli usuniesz * .config, aby działał na wszystkich plikach. Możesz dodać filtr -File do Get-ChildItem ... Zajęło trochę czasu, aby to
rozgryźć
32

PowerShell to dobry wybór;) Bardzo łatwo jest wyliczyć pliki w danym katalogu, czytać je i przetwarzać.

Skrypt mógłby wyglądać tak:

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

Podzieliłem kod na więcej wierszy, aby było dla Ciebie czytelne. Zauważ, że możesz użyć Set-Content zamiast [IO.File]::WriteAllText, ale dodaje on nową linię na końcu. Dzięki temu WriteAllTextmożesz tego uniknąć.

W przeciwnym razie kod może wyglądać następująco: $c | Set-Content $_.FullName.

stej
źródło
14

To podejście działa dobrze:

gci C:\Projects *.config -recurse | ForEach {
  (Get-Content $_ | ForEach {$_ -replace "old", "new"}) | Set-Content $_ 
}
  • Zmień „stare” i „nowe” na odpowiadające im wartości (lub użyj zmiennych).
  • Nie zapomnij o nawiasach - bez których otrzymasz błąd dostępu.
Tolga
źródło
Więc wybrałem ten dla zwięzłego wyrażenia - ale musiałem zastąpić Get-Content $_go Get-Content $_.FullNamei odpowiednikiem dla Set-Contentobsługi plików, które nie znajdowały się w katalogu głównym.
Matt Whitfield,
11

Poszedłbym z xml i 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
źródło
11

Ten przykład programu PowerShell szuka wszystkich wystąpień ciągu „\ foo \” w folderze i jego podfolderach, zamienia „\ foo \” na „\ bar \” ORAZ NIE NAPISUJE PONOWNIE plików, które nie zawierają ciągu „\ foo \ „W ten sposób nie niszczysz pliku ze znacznikami daty i godziny ostatniej aktualizacji, w których nie znaleziono ciągu:

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
źródło
7

Uważam, że komentarz @Artyom był przydatny, ale niestety nie opublikował odpowiedzi.

To jest skrócona, moim zdaniem najlepsza wersja zaakceptowanej odpowiedzi;

ls *.config -rec | %{$f=$_; (gc $f.PSPath) | %{$_ -replace "Dev", "Demo"} | sc $f.PSPath}
M--
źródło
1
Na wypadek, gdyby ktoś inny natknął się na to, tak jak ja - chcąc wykonać to bezpośrednio z pliku wsadowego - może pomóc użycie foreach-objectzamiast %aliasu podczas wykonywania takiego polecenia. W przeciwnym razie może to spowodować błąd:Expressions are only allowed as the first element of a pipeline
Dustin Halstead
Krótkie nie zawsze znaczy lepsze, jaśniej widać, co się dzieje w długiej wersji.
Nick N.
@NickN. Dobrze. To zależy od twojego celu. Chciałbym flag go jako POB;)
M--
6

Napisałem małą funkcję pomocniczą do zamiany tekstu w pliku:

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

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

Przykład:

Get-ChildItem . *.config -rec | ForEach-Object { 
    Replace-TextInFile -FilePath $_ -Pattern 'old' -Replacement 'new' 
}
Martin Brandl
źródło
4

Podczas rekurencyjnej zamiany należy podać ścieżkę i nazwę pliku:

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

Spowoduje to zamianę wszystkich „starych” na „nowe” z rozróżnianiem wielkości liter we wszystkich plikach w folderach bieżącego katalogu.

Geir Ivar Jerstad
źródło
Część Twojej odpowiedzi „.PSPath” naprawdę mi pomogła. Ale musiałem zmienić wewnętrzne „{$” na „$ _”. Zmieniłbym twoją odpowiedź, ale nie używam twojej części -creplace - używam zaakceptowanej odpowiedzi z .PSPath
aaaa bbbb