PowerShell과 폴더 및 컨텐츠 비교
저는 xml 파일이 있는 두 개의 다른 폴더를 가지고 있습니다.하나의 폴더(folder2)는 다른 폴더(folder1)와 비교하여 업데이트된 새 xml 파일을 포함합니다.폴더1과 비교하여 폴더2의 어떤 파일이 신규/업데이트되었는지 알고 제3의 폴더(폴더3)에 복사해야 합니다.PowerShell에서 이를 실현하는 가장 좋은 방법은 무엇입니까?
좋아요, 제가 전체를 코드화하지는 않겠지만(재미있는 점은 무엇인가요?) 시작하겠습니다.
첫째, 내용 비교는 두 가지 방법이 있습니다.파일 길이를 비교하는 게으른/대부분 올바른 방법과 각 파일의 내용에 대한 해시를 비교하는 정확하지만 더 관련된 방법입니다.
단순화를 위해 쉬운 방법으로 파일 크기를 비교해 보겠습니다.
기본적으로 원본 폴더와 대상 폴더를 나타내는 두 개체를 원합니다.
$Folder1 = Get-childitem "C:\Folder1"
$Folder2 = Get-childitem "C:\Folder2"
그러면 사용할 수 있습니다.Compare-Object
어떤 항목이 다른지 확인하는 것...
Compare-Object $Folder1 $Folder2 -Property Name, Length
각 컬렉션에서 파일 개체의 이름과 길이만을 비교하여 다른 모든 것을 나열합니다.
당신은 그것을 a에 배관할 수 있습니다.Where-Object
왼쪽에 있는 다른 것을 골라내기 위해 필터를...
Compare-Object $Folder1 $Folder2 -Property Name, Length | Where-Object {$_.SideIndicator -eq "<="}
를 대세요.ForEach-Object
원하는 위치에 복사하기:
Compare-Object $Folder1 $Folder2 -Property Name, Length | Where-Object {$_.SideIndicator -eq "<="} | ForEach-Object {
Copy-Item "C:\Folder1\$($_.name)" -Destination "C:\Folder3" -Force
}
MD5 해싱을 사용한 재귀적 디렉토리 차이(내용 비교)
다음은 각 디렉터리 파일 내용(좌/우)에 대한 MD5 해시를 계산하는 순수 PowerShell v3+ 재귀 파일 diff(의존 관계 없음)입니다.CSV를 요약 텍스트 파일과 함께 내보낼 수 있습니다.기본 출력 결과는 stdout입니다.rdiff.ps1 파일을 경로에 드롭하거나 스크립트에 내용을 복사할 수 있습니다.
USAGE: rdiff path/to/left,path/to/right [-s path/to/summary/dir]
요점은 이렇습니다.시간이 지남에 따라 추가 기능이 있을 수 있으므로 gist에서 버전을 사용하는 것이 좋습니다.당기기 요청은 언제든지 보내주시기 바랍니다.
#########################################################################
### USAGE: rdiff path/to/left,path/to/right [-s path/to/summary/dir] ###
### ADD LOCATION OF THIS SCRIPT TO PATH ###
#########################################################################
[CmdletBinding()]
param (
[parameter(HelpMessage="Stores the execution working directory.")]
[string]$ExecutionDirectory=$PWD,
[parameter(Position=0,HelpMessage="Compare two directories recursively for differences.")]
[alias("c")]
[string[]]$Compare,
[parameter(HelpMessage="Export a summary to path.")]
[alias("s")]
[string]$ExportSummary
)
### FUNCTION DEFINITIONS ###
# SETS WORKING DIRECTORY FOR .NET #
function SetWorkDir($PathName, $TestPath) {
$AbsPath = NormalizePath $PathName $TestPath
Set-Location $AbsPath
[System.IO.Directory]::SetCurrentDirectory($AbsPath)
}
# RESTORES THE EXECUTION WORKING DIRECTORY AND EXITS #
function SafeExit() {
SetWorkDir /path/to/execution/directory $ExecutionDirectory
Exit
}
function Print {
[CmdletBinding()]
param (
[parameter(Mandatory=$TRUE,Position=0,HelpMessage="Message to print.")]
[string]$Message,
[parameter(HelpMessage="Specifies a success.")]
[alias("s")]
[switch]$SuccessFlag,
[parameter(HelpMessage="Specifies a warning.")]
[alias("w")]
[switch]$WarningFlag,
[parameter(HelpMessage="Specifies an error.")]
[alias("e")]
[switch]$ErrorFlag,
[parameter(HelpMessage="Specifies a fatal error.")]
[alias("f")]
[switch]$FatalFlag,
[parameter(HelpMessage="Specifies a info message.")]
[alias("i")]
[switch]$InfoFlag = !$SuccessFlag -and !$WarningFlag -and !$ErrorFlag -and !$FatalFlag,
[parameter(HelpMessage="Specifies blank lines to print before.")]
[alias("b")]
[int]$LinesBefore=0,
[parameter(HelpMessage="Specifies blank lines to print after.")]
[alias("a")]
[int]$LinesAfter=0,
[parameter(HelpMessage="Specifies if program should exit.")]
[alias("x")]
[switch]$ExitAfter
)
PROCESS {
if($LinesBefore -ne 0) {
foreach($i in 0..$LinesBefore) { Write-Host "" }
}
if($InfoFlag) { Write-Host "$Message" }
if($SuccessFlag) { Write-Host "$Message" -ForegroundColor "Green" }
if($WarningFlag) { Write-Host "$Message" -ForegroundColor "Orange" }
if($ErrorFlag) { Write-Host "$Message" -ForegroundColor "Red" }
if($FatalFlag) { Write-Host "$Message" -ForegroundColor "Red" -BackgroundColor "Black" }
if($LinesAfter -ne 0) {
foreach($i in 0..$LinesAfter) { Write-Host "" }
}
if($ExitAfter) { SafeExit }
}
}
# VALIDATES STRING MIGHT BE A PATH #
function ValidatePath($PathName, $TestPath) {
If([string]::IsNullOrWhiteSpace($TestPath)) {
Print -x -f "$PathName is not a path"
}
}
# NORMALIZES RELATIVE OR ABSOLUTE PATH TO ABSOLUTE PATH #
function NormalizePath($PathName, $TestPath) {
ValidatePath "$PathName" "$TestPath"
$TestPath = [System.IO.Path]::Combine((pwd).Path, $TestPath)
$NormalizedPath = [System.IO.Path]::GetFullPath($TestPath)
return $NormalizedPath
}
# VALIDATES STRING MIGHT BE A PATH AND RETURNS ABSOLUTE PATH #
function ResolvePath($PathName, $TestPath) {
ValidatePath "$PathName" "$TestPath"
$ResolvedPath = NormalizePath $PathName $TestPath
return $ResolvedPath
}
# VALIDATES STRING RESOLVES TO A PATH AND RETURNS ABSOLUTE PATH #
function RequirePath($PathName, $TestPath, $PathType) {
ValidatePath $PathName $TestPath
If(!(Test-Path $TestPath -PathType $PathType)) {
Print -x -f "$PathName ($TestPath) does not exist as a $PathType"
}
$ResolvedPath = Resolve-Path $TestPath
return $ResolvedPath
}
# Like mkdir -p -> creates a directory recursively if it doesn't exist #
function MakeDirP {
[CmdletBinding()]
param (
[parameter(Mandatory=$TRUE,Position=0,HelpMessage="Path create.")]
[string]$Path
)
PROCESS {
New-Item -path $Path -itemtype Directory -force | Out-Null
}
}
# GETS ALL FILES IN A PATH RECURSIVELY #
function GetFiles {
[CmdletBinding()]
param (
[parameter(Mandatory=$TRUE,Position=0,HelpMessage="Path to get files for.")]
[string]$Path
)
PROCESS {
ls $Path -r | where { !$_.PSIsContainer }
}
}
# GETS ALL FILES WITH CALCULATED HASH PROPERTY RELATIVE TO A ROOT DIRECTORY RECURSIVELY #
# RETURNS LIST OF @{RelativePath, Hash, FullName}
function GetFilesWithHash {
[CmdletBinding()]
param (
[parameter(Mandatory=$TRUE,Position=0,HelpMessage="Path to get directories for.")]
[string]$Path,
[parameter(HelpMessage="The hash algorithm to use.")]
[string]$Algorithm="MD5"
)
PROCESS {
$OriginalPath = $PWD
SetWorkDir path/to/diff $Path
GetFiles $Path | select @{N="RelativePath";E={$_.FullName | Resolve-Path -Relative}},
@{N="Hash";E={(Get-FileHash $_.FullName -Algorithm $Algorithm | select Hash).Hash}},
FullName
SetWorkDir path/to/original $OriginalPath
}
}
# COMPARE TWO DIRECTORIES RECURSIVELY #
# RETURNS LIST OF @{RelativePath, Hash, FullName}
function DiffDirectories {
[CmdletBinding()]
param (
[parameter(Mandatory=$TRUE,Position=0,HelpMessage="Directory to compare left.")]
[alias("l")]
[string]$LeftPath,
[parameter(Mandatory=$TRUE,Position=1,HelpMessage="Directory to compare right.")]
[alias("r")]
[string]$RightPath
)
PROCESS {
$LeftHash = GetFilesWithHash $LeftPath
$RightHash = GetFilesWithHash $RightPath
diff -ReferenceObject $LeftHash -DifferenceObject $RightHash -Property RelativePath,Hash
}
}
### END FUNCTION DEFINITIONS ###
### PROGRAM LOGIC ###
if($Compare.length -ne 2) {
Print -x "Compare requires passing exactly 2 path parameters separated by comma, you passed $($Compare.length)." -f
}
Print "Comparing $($Compare[0]) to $($Compare[1])..." -a 1
$LeftPath = RequirePath path/to/left $Compare[0] container
$RightPath = RequirePath path/to/right $Compare[1] container
$Diff = DiffDirectories $LeftPath $RightPath
$LeftDiff = $Diff | where {$_.SideIndicator -eq "<="} | select RelativePath,Hash
$RightDiff = $Diff | where {$_.SideIndicator -eq "=>"} | select RelativePath,Hash
if($ExportSummary) {
$ExportSummary = ResolvePath path/to/summary/dir $ExportSummary
MakeDirP $ExportSummary
$SummaryPath = Join-Path $ExportSummary summary.txt
$LeftCsvPath = Join-Path $ExportSummary left.csv
$RightCsvPath = Join-Path $ExportSummary right.csv
$LeftMeasure = $LeftDiff | measure
$RightMeasure = $RightDiff | measure
"== DIFF SUMMARY ==" > $SummaryPath
"" >> $SummaryPath
"-- DIRECTORIES --" >> $SummaryPath
"`tLEFT -> $LeftPath" >> $SummaryPath
"`tRIGHT -> $RightPath" >> $SummaryPath
"" >> $SummaryPath
"-- DIFF COUNT --" >> $SummaryPath
"`tLEFT -> $($LeftMeasure.Count)" >> $SummaryPath
"`tRIGHT -> $($RightMeasure.Count)" >> $SummaryPath
"" >> $SummaryPath
$Diff | Format-Table >> $SummaryPath
$LeftDiff | Export-Csv $LeftCsvPath -f
$RightDiff | Export-Csv $RightCsvPath -f
}
$Diff
SafeExit
에도 @JNK 의에, 의 이지 않은 하는 것이 Compare-Object
. 당신은 단지 그를 이용하기만 하면 됩니다.-PassThru
스위치...
$Folder1 = Get-ChildItem "C:\Folder1"
$Folder2 = Get-ChildItem "C:\Folder2"
$Folder2 = "C:\Folder3\"
# Get all differences, i.e. from both "sides"
$AllDiffs = Compare-Object $Folder1 $Folder2 -Property Name,Length -PassThru
# Filter for new/updated files from $Folder2
$Changes = $AllDiffs | Where-Object {$_.Directory.Fullname -eq $Folder2}
# Copy to $Folder3
$Changes | Copy-Item -Destination $Folder3
이것은 적어도 사이드 인디케이터 화살표가 어느 방향을 가리키는지에 대해 걱정하지 않아도 된다는 것을 의미합니다!
또한 LastWriteTime(마지막 쓰기 시간)에서도 비교할 수 있습니다.
하위 폴더
목록을 비교하기 전에 FullName 필드에서 해당 루트 폴더 경로를 분리해야 하므로 하위 폴더를 재귀적으로 순환하는 것은 조금 더 복잡합니다.
이렇게 하려면 Folder1 및 Folder2 목록에 새 ScriptProperty를 추가합니다.
$Folder1 | Add-Member -MemberType ScriptProperty -Name "RelativePath" `
-Value {$this.FullName -replace [Regex]::Escape("C:\Folder1"),""}
$Folder2 | Add-Member -MemberType ScriptProperty -Name "RelativePath" `
-Value {$this.FullName -replace [Regex]::Escape("C:\Folder2"),""}
그런 다음 두 개체를 비교할 때 RelativePath를 속성으로 사용하고 이를 사용하여 "C:"에 가입할 수 있습니다.폴더 구조를 유지하기 위해 복사할 때 \Folder3".
내용이 없거나 다른 파일을 찾는 방법이 있습니다.
첫째, 빠르고 더러운 원라이너(아래 주의사항 참조).
dir -r | rvpa -Relative |%{ if (Test-Path $right\$_) { if (Test-Path -Type Leaf $_) { if ( diff (cat $_) (cat $right\$_ ) ) { $_ } } } else { $_ } }
위의 내용을 디렉토리 중 하나에서 실행합니다.$right
다른 디렉토리의 경로로 설정(또는 대체)됩니다.에서 빠진 것들$right
, 또는 내용이 다른 경우 보고됩니다.출력이 없음은 차이가 없음을 의미합니다.주의 사항:에 존재하는 것들$right
그러나 왼쪽에서 누락된 부분은 찾을 수 없습니다./reported.
해시를 계산하는 것은 번거롭지 않고 파일 내용을 직접 비교할 뿐입니다.해싱은 다른 맥락(나중에 날짜, 다른 기계 등)에서 무언가를 참조하고 싶을 때는 말이 되지만, 우리가 직접 비교할 때는 간접비만 추가합니다.(이론적으로 두 개의 파일이 동일한 해시를 갖는 것도 가능하지만, 기본적으로 실수로 발생하는 것은 불가능합니다.(반면 고의적인 공격...)
여기 더 많은 코너 사례와 오류를 처리하는 더 적절한 스크립트가 있습니다.
[CmdletBinding()]
Param(
[Parameter(Mandatory=$true,Position=0)][string]$Left,
[Parameter(Mandatory=$True,Position=1)][string]$Right
)
# throw errors on undefined variables
Set-StrictMode -Version 1
# stop immediately on error
$ErrorActionPreference = [System.Management.Automation.ActionPreference]::Stop
# init counters
$Items = $MissingRight = $MissingLeft = $Contentdiff = 0
# make sure the given parameters are valid paths
$left = Resolve-Path $left
$right = Resolve-Path $right
# make sure the given parameters are directories
if (-Not (Test-Path -Type Container $left)) { throw "not a container: $left" }
if (-Not (Test-Path -Type Container $right)) { throw "not a container: $right" }
# Starting from $left as relative root, walk the tree and compare to $right.
Push-Location $left
try {
Get-ChildItem -Recurse | Resolve-Path -Relative | ForEach-Object {
$rel = $_
$Items++
# make sure counterpart exists on the other side
if (-not (Test-Path $right\$rel)) {
Write-Output "missing from right: $rel"
$MissingRight++
return
}
# compare contents for files (directories just have to exist)
if (Test-Path -Type Leaf $rel) {
if ( Compare-Object (Get-Content $left\$rel) (Get-Content $right\$rel) ) {
Write-Output "content differs : $rel"
$ContentDiff++
}
}
}
}
finally {
Pop-Location
}
# Check items in $right for counterparts in $left.
# Something missing from $left of course won't be found when walking $left.
# Don't need to check content again here.
Push-Location $right
try {
Get-ChildItem -Recurse | Resolve-Path -Relative | ForEach-Object {
$rel = $_
if (-not (Test-Path $left\$rel)) {
Write-Output "missing from left : $rel"
$MissingLeft++
return
}
}
}
finally {
Pop-Location
}
Write-Verbose "$Items items, $ContentDiff differed, $MissingLeft missing from left, $MissingRight from right"
작업:
compare (Get-ChildItem D:\MyFolder\NewFolder) (Get-ChildItem \\RemoteServer\MyFolder\NewFolder)
그리고 심지어 재귀적으로:
compare (Get-ChildItem -r D:\MyFolder\NewFolder) (Get-ChildItem -r \\RemoteServer\MyFolder\NewFolder)
잊지 못할 정도로 :)
스크립트 매개 변수를 사용한 핸디 버전
간단한 파일 레벨 비교
이렇게 불러요.PS > .\DirDiff.ps1 -a .\Old\ -b .\New\
Param(
[string]$a,
[string]$b
)
$fsa = Get-ChildItem -Recurse -path $a
$fsb = Get-ChildItem -Recurse -path $b
Compare-Object -Referenceobject $fsa -DifferenceObject $fsb
가능한 출력:
InputObject SideIndicator
----------- -------------
appsettings.Development.json <=
appsettings.Testing.json <=
Server.pdb =>
ServerClientLibrary.pdb =>
gci-path 'C:\Folder' -recurse |where{$_.PSI 컨테이너}
- recurse는 주어진 루트 경로 및 의 아래에 있는 모든 하위 트리를 탐색합니다.PSIsContainer 속성은 모든 폴더만 가져오기 위해 테스트하려는 속성입니다.{!} 위치를 사용할 수 있습니다.$. . .PSIsContainer}은(는) 파일만 사용할 수 있습니다.
언급URL : https://stackoverflow.com/questions/6526441/comparing-folders-and-content-with-powershell
'bestsource' 카테고리의 다른 글
Powershell 설치 - 지정된 검색 조건 및 모듈 이름과 일치하는 항목을 찾을 수 없습니다. (0) | 2023.10.11 |
---|---|
윈도우에 PHP PDO 설치(xampp) (0) | 2023.10.11 |
PowerShell로 App.config 설정을 읽고 쓰는 방법은 무엇입니까? (0) | 2023.10.01 |
재스민을 사용하여 객체가 없는 기능을 염탐하기 (0) | 2023.10.01 |
여기서 날짜 시간이 특정 시간보다 오래됨(예: 15분) (0) | 2023.10.01 |