PowerShell二进制文件比较

13
大家好, 有一个应用程序生成它的导出数据。我需要编写一个脚本,将前一天的导出数据与最新的数据进行比较,如果它们之间存在差异,则需要进行一些基本的操作,如移动和删除等。 我尝试了一种适合我的方法,代码如下: $var_com=diff (get-content D:\local\prodexport2 -encoding Byte) (get-content D:\local\prodexport2 -encoding Byte) 我还尝试了Compare-Object cmdlet。我发现内存使用率很高,并且几分钟后我最终收到了一个System.OutOfMemoryException 的消息。你们中有人做过类似的事情吗?请给我一些想法。 之前有一个线程提到了一种哈希比较方法,但我不知道该怎么做。 谢谢。

你需要知道哪些字节是不同的,还是只需要知道今天的文件与昨天的文件不同? - mjolinor
只需要知道它们是否不同。正如您所引用的,我需要知道这些文件是否相同。 - user2967267
请查看这里的答案(https://dev59.com/QXVC5IYBdhLWcg3wsTZi)。它标记为C#,但由于它是.NET,因此可以转换为PowerShell语法。最简单的方法是首先比较文件大小-如果它们不同,则已经得到了答案。 - alroc
如果在Get-Content命令中使用-Raw参数且没有指定任何-Encoding,比较速度会更快、更容易。 - Serhii Kheilyk
5个回答

25

使用 PowerShell 4,您可以使用本地命令来执行此操作:

function CompareFiles {
    param(
    [string]$Filepath1,
    [string]$Filepath2
    )
    if ((Get-FileHash $Filepath1).Hash -eq (Get-FileHash $Filepath2).Hash) {
        Write-Host 'Files Match' -ForegroundColor Green
    } else {
        Write-Host 'Files do not match' -ForegroundColor Red
    }
}

PS C:> CompareFiles .\20131104.csv .\20131104-copy.csv

文件匹配

PS C:> CompareFiles .\20131104.csv .\20131107.csv

文件不匹配

如果你想在大规模上编程使用此函数,你可以轻松修改上述函数以返回$true或$false值


编辑

在看到这个答案后,我只想提供一个更大规模的版本,它只简单地返回truefalse

function CompareFiles 
{
    param
    (
        [parameter(
            Mandatory = $true,
            HelpMessage = "Specifies the 1st file to compare. Make sure it's an absolute path with the file name and its extension."
        )]
        [string]
        $file1,

        [parameter(
            Mandatory = $true,
            HelpMessage = "Specifies the 2nd file to compare. Make sure it's an absolute path with the file name and its extension."
        )]
        [string]
        $file2
    )

    ( Get-FileHash $file1 ).Hash -eq ( Get-FileHash $file2 ).Hash
}

13

您可以使用fc.exe。它与Windows一起提供。以下是如何使用它:

fc.exe /b d:\local\prodexport2 d:\local\prodexport1 > $null
if (!$?) {
    "The files are different"
}

2
我可能倾向于不使用 if (!$?),而是用 if ($LastExitCode -eq 0) 替换它。请查看 https://dev59.com/rGgv5IYBdhLWcg3wW_d_ 和所有答案。 - Code Maverick
1
这对于不同的文件来说非常慢,因为它会打印所有差异(到 null)。似乎 fc 不支持不打印输出。可以使用 'fc /a /b',它可能会尝试输出较少的内容,但对我来说没有太大的区别。 - arberg
只是出于好奇,将$null分配给有用吗?例如:$null = fc.exe ... - Keith Hill

8

前不久,我写了一篇关于使用PowerShell进行文件比较的缓冲比较例程的文章:

function FilesAreEqual {
    param(
        [System.IO.FileInfo] $first,
        [System.IO.FileInfo] $second, 
        [uint32] $bufferSize = 524288) 

    if ($first.Length -ne $second.Length) return $false

    if ( $bufferSize -eq 0 ) $bufferSize = 524288

    $fs1 = $first.OpenRead()
    $fs2 = $second.OpenRead()

    $one = New-Object byte[] $bufferSize
    $two = New-Object byte[] $bufferSize
    $equal = $true

    do {
        $bytesRead = $fs1.Read($one, 0, $bufferSize)
        $fs2.Read($two, 0, $bufferSize) | out-null

        if ( -Not [System.Linq.Enumerable]::SequenceEqual($one, $two)) {
            $equal = $false
        }

    } while ($equal -and $bytesRead -eq $bufferSize)

    $fs1.Close()
    $fs2.Close()

    return $equal
}

您可以这样使用它:

FilesAreEqual c:\temp\test.html c:\temp\test.html

哈希(如MD5)需要遍历整个文件才能进行哈希计算。此脚本在看到缓冲区中有差异时立即返回结果。它使用LINQ比本地PowerShell更快地比较缓冲区。

你的例程在性能方面与@ericnils的答案相比如何?当将其用于可能从包含各种大小文件的foreach中调用的函数内部时,您的代码是否比4.0 Get-FileHash更优化? - Code Maverick
@CodeMaverick,应该正是他所说的原因。除非两个文件完全相同,否则不必读取整个文件。这是最理想的解决方案。 - Nacht
1
我建议将$BYTES_TO_READ设置为比8更高的值。在我的系统上,每次迭代读取8个字节非常慢。我不知道最佳值是多少,但将缓冲区大小增加到32768(32 KB)肯定会使文件比较更快。 - herzbube
不幸的是,正如herzbube所指出的那样,当前的实现给出完全错误的答案,因为实际上只有32768个字节中的8个字节被比较。 - John Rees
1
@KeesC.Bakker - 九年过去了,我以你的代码为基础编写了一个函数,并在PowerShell中对比了其他二进制文件比较方法。结果发现,你的方法是最快的:https://stackoverflow.com/questions/76895989/speed-of-binary-file-comparisons-in-powershell/ - NewSites
显示剩余4条评论

8

另一种方法是比较文件的MD5哈希值:

$Filepath1 = 'c:\testfiles\testfile.txt'
$Filepath2 = 'c:\testfiles\testfile1.txt'

$hashes = 
foreach ($Filepath in $Filepath1,$Filepath2)
{
 $MD5 = [Security.Cryptography.HashAlgorithm]::Create( "MD5" )
 $stream = ([IO.StreamReader]"$Filepath").BaseStream
 -join ($MD5.ComputeHash($stream) | 
 ForEach { "{0:x2}" -f $_ })
 $stream.Close()
 }

if ($hashes[0] -eq $hashes[1])
  {'Files Match'}

谢谢这个。它消除了以前比较所需的长时间。 - user2967267
我尝试使用相对路径运行此代码(在 Powershell 中 cd somewhere 然后 $FilePath1 = 'testfile.txt'),但是 StreamReader 没有捕捉到 Powershell 的文件夹更改,而是认为它是相对于我的主文件夹的。解决方法是使用 $Filepath1 = Get-Item 'testfile.txt',然后 Powershell 会传递正确的绝对路径给 StreamReader。 - Duncan
1
PowerShell的Get-FileHash函数现在可用,并且更简单地执行相同的操作。 - NoBrassRing

2
if ( (Get-FileHash c:\testfiles\testfile1.txt).Hash -eq (Get-FileHash c:\testfiles\testfile2.txt).Hash ) {
   Write-Output "Files match"
} else {
   Write-Output "Files do not match"
}

1
你好,欢迎来到stackoverflow,并感谢您的回答。虽然这段代码可能回答了问题,但您是否可以考虑添加一些解释,说明您解决的问题以及如何解决它?这将有助于未来的读者更好地理解您的答案并从中学习。 - Plutian

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接