目次
検証環境
- .NET Core 3.1
- LINQ Pad 6
ZIP を操作する
.NET Core で ZIP を操作するには、ZipFile
、ZipArchive
、ZipArchiveEntry
などを使う。
ZipFile
はパスを必要としディスクへの読み書きに使う。
ZipArchive
は初期化に Stream
を必要とするため、メモリ上で操作するのに向いている。
ZipArchiveEntry
は ZIP に含まれるファイルもしくは空のフォルダの情報を表す。
以降のサンプルコードでは次の変数を使用する。 また実行および出力結果は LINQPad で行っている。
// 圧縮する対象フォルダ var targetDir = @"C:\ZipTest\ParentDir"; // 圧縮先 var zipPath = @"C:\ZipTest\test.zip"; // 展開先 var extractPath = @"C:\ZipTest\test";
targetDir
配下の構成は以下の通りになっている。
圧縮する
ZipFile.CreateFromDirectory(targetDir, zipPath);
引数でエンコードを指定しない場合、 フォルダ名およびファイル名が ASCII 文字のみで構成されているとシステムのデフォルトコードページが使用される。 ASCII 文字以外を含む場合は、UTF-8 が使用される。
展開する
ZipFile.ExtractToDirectory(zipPath, extractPath);
引数でエンコードを指定した場合も指定しない場合も、ファイルヘッダーにフラグが設定されている場合、UTF-8 を使用してデコードされる。
ファイルヘッダーにフラグが設定されていない場合は、引数でエンコードを指定すると指定したエンコードでデコードされる。エンコードを指定しないとシステムのデフォルトコードページが使用される。
一般的に Windows では Shift-JIS
、Linux や Mac OS などでは UTF-8
が使用されるため、外部のツールなどで圧縮を行った場合は注意が必要である。
(ヘッダーを直接読めれば UTF-8 かそれ以外かの判定ができる?かも?)
ZIP 内のファイルを列挙する
using (var archive = ZipFile.OpenRead(zipPath)) { foreach (var entry in archive.Entries) { entry.FullName.Dump(); } }
出力結果は次の通り。
空のフォルダはフォルダ単体で ZipArchive.Entries
プロパティに含まれるが、ファイルを配下に持つフォルダはフォルダ単体では含まれない。フォルダは末尾に /
が付く。
一部だけ展開する
using (var archive = ZipFile.OpenRead(zipPath)) { var fileEntry = archive.GetEntry("SubDir/SubDirMemo1.txt"); fileEntry.ExtractToFile(Path.Combine(extractPath, "SubDirMemo1_copy.txt"), true); var dirEntry = archive.GetEntry("EmptyDir/"); dirEntry.ExtractToFile(Path.Combine(extractPath, "EmptyDir_copy"), true); }
ファイルを持つフォルダはエントリーの取得ができないため、フォルダを指定して展開することはできない。また、空のフォルダのエントリーは取得できてもファイルではないので展開できない。空のファイルとして書き出される。
ファイルを削除する
using (var archive = ZipFile.Open(zipPath, ZipArchiveMode.Update)) { var fileEntry = archive.GetEntry("SubDir/SubDirMemo2.txt"); fileEntry.Delete(); }
ファイルを追加する
using (var archive = ZipFile.Open(zipPath, ZipArchiveMode.Update)) { archive.CreateEntryFromFile(Path.Combine(targetDir, "Memo1.txt"), "AddedDir/Memo1_copy.txt"); }
展開せずにテキストファイルの内容を取得する
using (var archive = ZipFile.OpenRead(zipPath)) { var fileEntry = archive.GetEntry("Memo1.txt"); using (var reader = new StreamReader(fileEntry.Open())) { reader.ReadToEnd().Dump(); } }
サンプルコード全文
// 圧縮する対象フォルダ var targetDir = @"C:\ZipTest\ParentDir"; // 圧縮先 var zipPath = @"C:\ZipTest\test.zip"; // 展開先 var extractPath = @"C:\ZipTest\test"; if (File.Exists(zipPath)) File.Delete(zipPath); if (Directory.Exists(extractPath)) Directory.Delete(extractPath, true); // 圧縮 ZipFile.CreateFromDirectory(targetDir, zipPath); // 展開 ZipFile.ExtractToDirectory(zipPath, extractPath); // ファイルの列挙 using (var archive = ZipFile.OpenRead(zipPath)) { foreach (var entry in archive.Entries) { // 空のフォルダは含まれるが、ファイルを持つフォルダはフォルダ単体としては含まれない entry.FullName.Dump(); } } // 一部だけ展開する using (var archive = ZipFile.OpenRead(zipPath)) { var fileEntry = archive.GetEntry("SubDir/SubDirMemo1.txt"); fileEntry.ExtractToFile(Path.Combine(extractPath, "SubDirMemo1_copy.txt"), true); // 空のフォルダは取得できてもファイルではないので展開できない(空のファイルとして書き出される) var dirEntry = archive.GetEntry("EmptyDir/"); dirEntry.ExtractToFile(Path.Combine(extractPath, "EmptyDir_copy"), true); } using (var archive = ZipFile.OpenRead(zipPath)) { archive.Entries.Select(x => x.FullName).Dump("元のZIP"); } // ファイルを削除する using (var archive = ZipFile.Open(zipPath, ZipArchiveMode.Update)) { var fileEntry = archive.GetEntry("SubDir/SubDirMemo2.txt"); //fileEntry.Delete(); archive.Entries.Select(x => x.FullName).Dump("削除後のZIP"); } // ファイルを追加する using (var archive = ZipFile.Open(zipPath, ZipArchiveMode.Update)) { archive.CreateEntryFromFile(Path.Combine(targetDir, "Memo1.txt"), "AddedDir/Memo1_copy.txt"); archive.Entries.Select(x => x.FullName).Dump("追加後のZIP"); } // 展開せずにテキストファイルの内容を取得する using (var archive = ZipFile.OpenRead(zipPath)) { var fileEntry = archive.GetEntry("Memo1.txt"); using (var reader = new StreamReader(fileEntry.Open())) { reader.ReadToEnd().Dump(); } }
ZipArchive
Form に ZIP ファイルを添付して送られてきた場合や、Azure Blob Storage から ZIP ファイルをメモリにダウンロードした場合など、ZIP ファイルのデータを Stream
として取得できる場合は ZipAcrhive
を直接使う。
using (var stream = form.Files[0].OpenReadStream()) using (var zip = new ZipArchive(stream, ZipArchiveMode.Read, false, Encoding.GetEncoding("Shift_JIS"))) { }
using (var stream = new MemoryStream()) { await blobClient.DownloadToAsync(stream); using (var zip = new ZipArchive(stream, ZipArchiveMode.Read, true, Encoding.GetEncoding("Shift_JIS"))) { } }
第3引数の leaveOpen
は、ZipArchive
を Dispose
した後も Stream
を開いたままにするかどうかのフラグ。