PowerShell 分割和合并大文件


PowerShell 分割和合并大文件 3


本文索引
[隐藏]
  • 1背景
  • 2需求
  • 3技术要点
  • 4截图
    • 4.1进度条显示
    • 4.2分割后的文件列表
  • 5使用示例
  • 6源脚本(Split-Merge-File.ps1)

背景

有同事提出将一个8G多的VHD上传到百度的网盘,但是百度网盘针对免费用户暂时的单个文件限制为4G,已经挺大的了,但是还是装不下这个VHD。最经济的最方便的方法就是将大文件分割成小文件,既解决了无法上传的问题,也降低了中途上传失败的概率(尽管支持断点续传)。假如网络带宽允许,将操作系统安装在网盘上,也是一件挺有意思的事情。下次使用时,再下载多个文件,然后合并。

需求

  • 写一个PowerShell 脚本,至少包含两个函数“分割文件”和“合并文件”;
  • 分割文件可以按照文件个数分割,也可以按照单个文件的大小分割;
  • 将脚本处理时占用的内存最大控制在50M左右;
  • 合并文件时接受待合并文件所在的目录为参数,并支持通配符过滤;
  • 在处理文件时,能够显示进度提示。

技术要点

  • 处理的一般都是大文件,所以使用.NET 中 FileStream 对象,因为流处理可以提高性能。
  • 将缓冲区设置为1M-50M,当分割的单个文件大小超过1G时,使用50M内存,小于等于1M时,使用1M,其余按比例增加力求节省内存。
  • 暂时不考虑使用并行处理,因为在此场景中性能更多由硬盘的读取速度决定。
  • 分割出的文件在源文件名称后追加_part_001 这样的格式,方便在合并前按照升序排序。

截图

进度条显示

分割文件进度条

分割后的文件列表

分割后的文件列表

使用示例

# 先导入 函数 Split-Merge-File
'E\Split-Merge-File.ps1' #注意句号和脚本之间有空格
 
# 将文件 ‘E:\win2012.vhdx’ 分割成20个小文件,输出至目录 'E:\VHD'
Split-File -File 'E:\win2012.vhdx' -ByPartCount -PartCount 20 -OutputDir 'E:\VHD'
 
# 将件‘E:\win2012.vhdx’按照每个大小 500MB 来分割,输出至目录 'E:\VHD'
Split-File -File 'E:\win2012.vhdx' -ByPartLength -PartLength 500MB -OutputDir 'E:\VHD'
 
# 将 'E:\VHD' 目录下包含 part 的所有文件合并,输出为 单个文件 'E:\win2012-2.vhdx'
Merge- File -SourceDir 'E:\VHD' -Filter "*part*" -OutputFile 'E:\win2012-2.vhdx'

源脚本(Split-Merge-File.ps1)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
# Obtain a suitable buffer length by partial file length
function Get-BufferLength ( [int] $partialFileLength )
{
     [int] $MinBufferLength = 1MB
     # No need to consume great amount memory,initialize as 50M, you can adjust it from here.
     [int] $MaxBufferLength = 50MB
 
     if ( $partialFileLength -ge 1GB) {  return $MaxBufferLength }
     elseif $partialFileLength -le 50MB) {  return $MinBufferLength }
     else return [int] $MaxBufferLength /1GB *  $partialFileLength )}
}
 
# Write partial stream to file from current position
function Write-PartialStreamToFile
{
     param (
     [IO.FileStream] $stream ,
     [long] $length ,
     [string] $outputFile
     )
 
     #copy stream to file
     function Copy-Stream [int] $bufferLength )
     {
         [byte[]] $buffer New-Object byte[](  $bufferLength )
 
         # Read partial file data to memory buffer
         $stream .Read( $buffer ,0, $buffer .Length) |  Out-Null
 
         # Flush buffer to file
         $outStream New-Object IO.FileStream( $outputFile , 'Append' , 'Write' , 'Read' )
         $outStream .Write( $buffer ,0, $buffer .Length)
         $outStream .Flush()
         $outStream .Close()
     }
 
     $maxBuffer Get-BufferLength $length
     $remBuffer = 0
     $loop [Math] ::DivRem( $length , $maxBuffer , [ref] $remBuffer )
 
     if ( $loop -eq 0)
     {
         Copy-Stream $remBuffer
         return
     }
 
     1.. $loop foreach {
         $bufferLength $maxBuffer
 
         # let last loop contains remanent length
         if ( ( $_ -eq $loop -and ( $remBuffer -gt 0) ) 
         {
             $bufferLength $maxBuffer $remBuffer
          }
          Copy-Stream  $bufferLength
 
          # show outer progress
          $progress [int] ( $_ *100/ $loop )
          write-progress -activity 'Writting file' -status 'Progress' -id 2 -percentcomplete $progress -currentOperation "$progress %"
     }
}
 
# split a large file into mutiple parts by part count or part length
function Split-File
{
     param (
     [ Parameter ( Mandatory = $True )]
     [IO.FileInfo] $File ,
     [Switch] $ByPartCount ,
     [Switch] $ByPartLength ,
     [int] $PartCount ,
     [int] $PartLength ,
     [IO.DirectoryInfo] $OutputDir '.'
     )
 
     # Argument validation
     if ( -not $File .Exists) { throw  "Source file [$File] not exists" }
     if ( -not  $OutputDir .Exists) { mkdir  $OutputDir .FullName |  Out-Null }
     if ( ( -not $ByPartCount -and ( -not $ByPartLength ) )
     {
         throw  'Must specify one of parameter, [ByPartCount] or [ByPartLength]'
     }
     elseif $ByPartCount )
     {
         if ( $PartCount -le 1) {throw  '[PartCount] must larger than 1' }
         $PartLength $File .Length /  $PartCount
     }
     elseif $ByPartLength )
     {
         if ( $PartLength -lt 1) { throw  '[PartLength] must larger than 0' }
         if ( $PartLength -ge $File .Length) { throw  '[PartLength] must less than source file' }
         $temp $File .Length / $PartLength
         $PartCount [int] $temp
         if ( ( $File .Length %  $PartLength -gt -and $PartCount -lt $temp ) )
         {
           $PartCount ++
         }
     }
 
     $stream New-Object IO.FileStream( $File .FullName,
     [IO.FileMode] ::Open , [IO.FileAccess] ::Read , [IO.FileShare] ::Read )
 
     # Make sure each part file name ended like '001' so that it's convenient to merge
     [string] $numberMaskStr [string] ::Empty.PadLeft(  [int] ( [Math] ::Log10( $PartCount ) + 1),  "0" )
 
     1 ..  $PartCount foreach {
          $outputFile Join-Path $OutputDir "{0}.part_{1} " -f $File .Name , $_ .ToString(  $numberMaskStr ) )
          # show outer progress
          $progress [int] ( $_ *100/ $PartCount )
          write-progress -activity "Splitting file" -status "Progress $progress %" -Id 1 -percentcomplete $progress -currentOperation "Handle file $outputFile"
          if ( $_ -eq $PartCount )
          {
             Write-PartialStreamToFile $stream ( $stream .Length -  $stream . Position ) $outputFile
          }
          else
          {
          Write-PartialStreamToFile $stream $PartLength  $outputFile
          }
     }
     $stream .Close()
}
 
function Merge -File
{
     param (
     [ Parameter ( Mandatory = $True )]
     [IO.DirectoryInfo] $SourceDir ,
     [string] $Filter ,
     [IO.FileInfo] $OutputFile
     )
 
     # arguments validation
     if -not $SourceDir .Exists ) { throw  "Directory $SourceDir not exists." }
     $files = dir  $SourceDir -File -Filter $Filter
     if ( $files -eq $null ){ throw  "No matched file in directory $SourceDir" }
 
     # output stream
     $outputStream New-Object IO.FileStream( $OutputFile .FullName,
         [IO.FileMode] ::Append , [IO.FileAccess] ::Write , [IO.FileShare] ::Read )
 
     # merge file
     $files foreach {
         #input stream
         $inputStream New-Object IO.FileStream( $_ .FullName,
         [IO.FileMode] ::Open , [IO.FileAccess] ::Read , [IO.FileShare] ::Read )
 
         $bufferLength Get-BufferLength -partialFileLength $_ .Length
         while ( $inputStream . Position -lt $inputStream .Length)
         {
             if ( ( $inputStream . Position $bufferLength -gt $inputStream .Length)
             {
                 $bufferLength $inputStream .Length -  $inputStream . Position
             }
 
             # show outer progress
             $progress [int] ( $inputStream . Position *100/  $inputStream .Length)
             write-progress -activity 'Merging file' -status "Progress $progress %"  -percentcomplete $progress
 
             # read file to memory buffer
             $buffer New-Object byte[](  $bufferLength )
             $inputStream .Read(  $buffer ,0, $buffer .Length) |  Out-Null
 
             #flush buffer to file
             $outputStream .Write(  $buffer ,0, $buffer .Length) |  Out-Null
             $outputStream .Flush()
         }
         $inputStream .Close()
     }
     $outputStream .Close()
}
本文链接:  http://www.pstips.net/powershell-split-merge-large-file.html
请尊重原作者和编辑的辛勤劳动,欢迎转载,并注明出处!

你可能感兴趣的:(知识扩展类文章)