やりたいこと
7zipに zipを作ったあとに、指定したサイズごとに ファイルを分割する機能があります。
メーラーで添付ファイルサイズに制限がある場合や、ファイルアップロードツールなどでアップロードサイズに制限がある場合などに、ファイルのやりとりをするときに、役に立つ機能だと思います。
7zipがインストールできない・してはいけない などの場合に、Windowsの標準機能だけで、他にアプリをインストールすることなく、同じことができないかを考えてみました。
まず、zipを作ること自体は、Explorerで普通にできます。 問題は、できたzipを一定のサイズごとに分割することだと思います。
#解決策
PowerShellでこれをやってみました。
InFIlePathで指定したファイル を SplitSizeバイトごとに 同じフォルダに連番で分割します。
サンプルでは 1 * 1024 * 1024 = 1MBで分割します。
たええば、1GBごとなら 1024 * 1024 * 1024 を指定してください。
# 何バイトずつで分割するかの 指定
$SplitSize = 1 * 1024 * 1024
#対象ファイルの指定
$InFilePath = "C:\ZippedData\test.zip"
$InFileName = Split-Path $InFilePath -leaf
$Folder = Split-Path $InFilePath -Parent -Resolve
$InStream = [System.IO.FileStream]::new($InFilePath, [System.IO.FileMode]::Open)
$FileCount = 0
$MaxFileCount = [Math]::Ceiling($InStream.Length / $SplitSize)
$Format = "d" + $MaxFileCount.ToString().Length
$Buffer = New-Object byte[] $SplitSize
$JoinCommand = ""
while($true){
$ByteCount = $InStream.Read($Buffer, 0, $SplitSize)
if($ByteCount -le 0){
break
}
$FileCount++
$OutFilePath = $InStream.Name + "." +$FileCount.ToString($Format)
$OutFileName = Split-Path $OutFilePath -leaf
$OutStream = [System.IO.FileStream]::new($OutFilePath, [System.IO.FileMode]::Create, [System.IO.FileAccess]::Write)
$OutStream.Write($Buffer, 0, $ByteCount)
}
実行結果は、こうなります。
これでファイルを送りたい相手が 7zip を持っていれば、 相手は 7zip でファイルを結合・展開することができます。
次に、自分も 7zipを持っていないが、相手も、また、7zipを持っていない場合を検討しました。
分割したファイルは、Windowsのコマンドプロンプトで、次のようにすることで、結合できます。
copy /b ファイル1つ目 + ファイル2つ目 + ファイル3つ名 + .... 最後のファイル 元のファイル名
これと同じことをする bat ファイルを自動生成しながら、ファイルを分割する PowerShell を考えます。
# 何バイトずつで分割するかの 指定
$SplitSize = 1 * 1024 * 1024
#対象ファイルの指定
$InFilePath = "C:\ZippedData\test.zip"
$InFileName = Split-Path $InFilePath -leaf
$Folder = Split-Path $InFilePath -Parent -Resolve
$InStream = [System.IO.FileStream]::new($InFilePath, [System.IO.FileMode]::Open)
$FileCount = 0
$MaxFileCount = [Math]::Ceiling($InStream.Length / $SplitSize)
$Format = "d" + $MaxFileCount.ToString().Length
$Buffer = New-Object byte[] $SplitSize
$JoinCommand = ""
while($true){
$ByteCount = $InStream.Read($Buffer, 0, $SplitSize)
if($ByteCount -le 0){
break
}
$FileCount++
$OutFilePath = $InStream.Name + "." +$FileCount.ToString($Format)
$OutFileName = Split-Path $OutFilePath -leaf
if($FileCount -eq 1){
$JoinCommand = "copy ${OutFileName} ${InFileName}.odd /Y`r`n"
} elseif ($FileCount % 2 -eq 0) {
$JoinCommand += "copy /b ${InFileName}.odd + ${OutFileName} ${InFileName}.even /Y`r`n"
} else {
$JoinCommand += "copy /b ${InFileName}.even + ${OutFileName} ${InFileName}.odd /Y`r`n"
}
$OutStream = [System.IO.FileStream]::new($OutFilePath, [System.IO.FileMode]::Create, [System.IO.FileAccess]::Write)
$OutStream.Write($Buffer, 0, $ByteCount)
}
if ($FileCount % 2 -eq 0) {
$JoinCommand += "ren ${InFileName}.even ${InFileName}`r`ndel ${InFileName}.odd`r`n"
} else {
$JoinCommand += "ren ${InFileName}.odd ${InFileName} `r`ndel ${InFileName}.even`r`n"
}
$JoinCommand | Set-Content (Join-Path $Folder "${InfileName}.join.bat") -Encoding default
最初と比べると batファイルが 追加で作成されています。
このbatの中は、こうなっています。
copy test.zip.1 test.zip.odd /Y
copy /b test.zip.odd + test.zip.2 test.zip.even /Y
copy /b test.zip.even + test.zip.3 test.zip.odd /Y
copy /b test.zip.odd + test.zip.4 test.zip.even /Y
ren test.zip.even test.zip
del test.zip.odd
連番ファイルを奇数番目結合後ファイルと偶数番目結合用ファイルに順に結合しながら、最終的に元のファイルに結合しなおすbat が自動生成されています。
このbat を 分割後のファイルと一緒に相手に送ることで、相手は ファイルの結合・展開を 7zipなしでできることが期待できます。
#コードの補足
下の部分は、ファイルの連番を 何桁で作成するかを決めています。 1 なのか 01 なのか 001 なのかを決めています。
$MaxFileCount = [Math]::Ceiling($InStream.Length / $SplitSize)
$Format = "d" + $MaxFileCount.ToString().Length
また、下の部分ですが
Get-Contentはとても処理が遅いので、使わずに、.Net の 仕組みを使っています。
Readメソッドでは、最大の読み込みサイズを指定すると、その範囲でファイルを読み込み、読み込んだバイト数を返してくれます。こうすることで、分割サイズごとの大きさで読み込みつつも、最後の半端なサイズ分もよきにはからってくれます。
読み込んだバイト数が0であれば、ファイルの終わりに達したと判断できます。
$InStream = [System.IO.FileStream]::new($InFilePath, [System.IO.FileMode]::Open)
・・・中略・・・
$ByteCount = $InStream.Read($Buffer, 0, $SplitSize)
if($ByteCount -le 0){
break
}