如何使用SharpZipLib向现有TAR文件添加条目?
向现有TAR文件添加条目的正确方式
问题原因
TAR文件有固定结构规则:
- 每个文件条目由512字节的头部 + 按512字节对齐的文件数据组成
- 文件末尾必须以两个连续的512字节空块作为结束标记
你的现有代码存在两个核心问题:
- 使用
FileMode.Open时,默认从文件开头写入,直接覆盖原有内容 - 使用
FileMode.Append时,新条目被写入到结束标记之后,TAR读取工具只会识别结束标记前的内容,后续数据被判定为"额外数据"无法识别
解决方案
修改TarFileWriter类,处理现有TAR文件的结束标记,将新条目插入到结束标记之前,写入完成后重新生成合法的结束标记。
修改后的TarFileWriter类
namespace MyNamespace { using System; using System.IO; using ICSharpCode.SharpZipLib.Tar; public class TarFileWriter : IFileWriter { private const int TarBlockSize = 512; private Stream FileStream { get; } private TarOutputStream OutputStream { get; } private bool _isAppending; public TarFileWriter(string filename, FileMode mode) { FileStream = File.Open(filename, mode); _isAppending = mode != FileMode.CreateNew && mode != FileMode.Create; if (_isAppending) { // 定位到文件末尾,检查并跳过TAR的结束标记(两个512字节空块) if (FileStream.Length >= 2 * TarBlockSize) { FileStream.Seek(-2 * TarBlockSize, SeekOrigin.End); var buffer = new byte[TarBlockSize]; // 验证第一个结束块是否为空 int read = FileStream.Read(buffer, 0, TarBlockSize); if (read == TarBlockSize && Array.TrueForAll(buffer, b => b == 0)) { // 验证第二个结束块是否为空 read = FileStream.Read(buffer, 0, TarBlockSize); if (read == TarBlockSize && Array.TrueForAll(buffer, b => b == 0)) { // 两个空块都存在,将指针移到结束块之前,准备写入新条目 FileStream.Seek(-2 * TarBlockSize, SeekOrigin.End); } else { // 不存在完整结束块,回到文件末尾 FileStream.Seek(0, SeekOrigin.End); } } else { // 第一个空块不存在,回到文件末尾 FileStream.Seek(0, SeekOrigin.End); } } } OutputStream = new TarOutputStream(FileStream, null); } public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } protected virtual void Dispose(bool disposing) { if (disposing) { // 追加模式下,确保写入合法的TAR结束标记 if (_isAppending) { OutputStream.Finish(); } OutputStream.Dispose(); FileStream.Dispose(); } } public void WriteAllBytes(string filename, byte[] bytes) { var tarHeader = new TarHeader { Name = filename, ModTime = DateTime.Now, }; var entry = new TarEntry(tarHeader) { Size = bytes.Length, }; OutputStream.PutNextEntry(entry); OutputStream.Write(bytes, 0, bytes.Length); OutputStream.CloseEntry(); } } // 补充IFileWriter接口定义(若项目中未定义) public interface IFileWriter : IDisposable { void WriteAllBytes(string filename, byte[] bytes); } }
正确的测试调用方式
[TestClass] public class FileToolTest { [TestMethod] public void CreateTarFile() { using (var tarFileWriter = new TarFileWriter(@"H:\TestTar.tar", FileMode.CreateNew)) { tarFileWriter.WriteAllBytes("Test.data", new byte[] { 12, 34, 56 }); } } [TestMethod] public void AddEntriesToExistingTar() { // 使用FileMode.Open即可,构造函数会自动处理追加逻辑 using (var tarFileWriter = new TarFileWriter(@"H:\TestTar.tar", FileMode.Open)) { tarFileWriter.WriteAllBytes("Test1.data", new byte[] { 12, 34, 56 }); tarFileWriter.WriteAllBytes("Test2.data", new byte[] { 78, 90, 12 }); } } }
关键说明
- 结束标记处理:打开现有TAR文件时,先检查末尾的两个空块,将文件指针移动到结束标记之前,确保新条目写入到合法位置
- 结束标记重写:追加完成后调用
OutputStream.Finish(),自动写入两个空块作为新的结束标记,保证TAR文件结构合法 - 模式选择:追加时使用
FileMode.Open即可,无需使用FileMode.Append,构造函数已处理所有追加逻辑
内容的提问来源于stack exchange,提问作者Peter Buchmann
相关产品推荐
相关产品推荐

