/**
* zipzstd.c - 支持多种压缩算法的 ZIP 工具
* 支持参数: 通配符, 递归, 压缩级别, 压缩算法, 输出文件, 解压缩, 排除顶级目录
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <zip.h>
#include <dirent.h>
#include <sys/stat.h>
#include <fnmatch.h>
#include <errno.h>
#include <getopt.h>
#include <libgen.h>
#include <unistd.h>
#ifdef _WIN32
#include <direct.h>
#define PATH_SEPARATOR '\\'
#else
#define PATH_SEPARATOR '/'
#endif
// 压缩算法类型
typedef enum {
COMPRESSION_ZSTD,
COMPRESSION_ZLIB,
COMPRESSION_BZIP2,
COMPRESSION_STORE
} CompressionType;
// 配置结构体
typedef struct {
int compress; // 1=压缩, 0=解压
int recursive; // 递归子目录
int compression_level; // 压缩级别
CompressionType comp_type; // 压缩算法类型
char *output_file; // 输出文件名
int no_top_dir; // 不包括顶级目录
char **input_patterns; // 输入文件模式
int pattern_count; // 模式数量
} Config;
// 文件信息结构体
typedef struct {
char *path; // 文件完整路径
char *archive_path; // 在压缩包中的路径
struct stat file_stat; // 文件状态信息
} FileInfo;
// 全局文件列表
FileInfo *file_list = NULL;
size_t file_count = 0;
size_t file_capacity = 0;
// 初始化配置
void init_config(Config *config) {
config->compress = 1;
config->recursive = 0;
config->compression_level = 6;
config->comp_type = COMPRESSION_ZSTD;
config->output_file = NULL;
config->no_top_dir = 0;
config->input_patterns = NULL;
config->pattern_count = 0;
}
// 释放配置资源
void free_config(Config *config) {
if (config->output_file) free(config->output_file);
for (int i = 0; i < config->pattern_count; i++) {
free(config->input_patterns[i]);
}
free(config->input_patterns);
}
// 解析压缩算法参数
int parse_compression_type(const char *type_str, CompressionType *type) {
if (strcmp(type_str, "zstd") == 0) {
*type = COMPRESSION_ZSTD;
return 0;
} else if (strcmp(type_str, "zlib") == 0) {
*type = COMPRESSION_ZLIB;
return 0;
} else if (strcmp(type_str, "bzip2") == 0) {
*type = COMPRESSION_BZIP2;
return 0;
} else if (strcmp(type_str, "store") == 0) {
*type = COMPRESSION_STORE;
return 0;
}
return -1;
}
// 获取压缩方法常量
zip_uint16_t get_compression_method(CompressionType type) {
switch (type) {
case COMPRESSION_ZSTD: return ZIP_CM_ZSTD;
case COMPRESSION_ZLIB: return ZIP_CM_DEFLATE;
case COMPRESSION_BZIP2: return ZIP_CM_BZIP2;
case COMPRESSION_STORE: return ZIP_CM_STORE;
default: return ZIP_CM_ZSTD;
}
}
// 获取压缩算法名称
const char *get_compression_name(CompressionType type) {
switch (type) {
case COMPRESSION_ZSTD: return "Zstandard";
case COMPRESSION_ZLIB: return "Zlib/Deflate";
case COMPRESSION_BZIP2: return "BZIP2";
case COMPRESSION_STORE: return "Store (无压缩)";
default: return "Unknown";
}
}
// 打印帮助信息
void print_help(const char *program_name) {
printf("用法: %s [选项] <文件/模式...>\n", program_name);
printf("选项:\n");
printf(" -d 解压缩模式\n");
printf(" -r 递归处理子目录\n");
printf(" -数字 压缩级别 (1-22, 默认:6)\n");
printf(" -en算法 压缩算法: zstd, zlib, bzip2, store (默认:zstd)\n");
printf(" -o文件 输出文件名\n");
printf(" -notop 不包括顶级目录\n");
printf(" -h 显示帮助信息\n");
printf("\n示例:\n");
printf(" %s *.txt -r -en zstd -9 -o archive.zip\n", program_name);
printf(" %s -d archive.zip -o ./output\n", program_name);
printf(" %s docs/ -r -en bzip2 -o docs.bzip2.zip\n", program_name);
}
// 检查文件是否匹配模式
int matches_pattern(const char *filename, Config *config) {
if (config->pattern_count == 0) return 1;
for (int i = 0; i < config->pattern_count; i++) {
if (fnmatch(config->input_patterns[i], filename, 0) == 0) {
return 1;
}
}
return 0;
}
// 解析命令行参数
int parse_arguments(int argc, char *argv[], Config *config) {
int opt;
while ((opt = getopt(argc, argv, "drT:o:en:h")) != -1) {
switch (opt) {
case 'd':
config->compress = 0;
break;
case 'r':
config->recursive = 1;
break;
case 'T':
// 保留线程参数但不使用(为了兼容性)
break;
case 'o':
config->output_file = strdup(optarg);
break;
case 'e':
// -en 参数处理
if (optarg && strncmp(optarg, "n", 1) == 0) {
const char *algo = optarg + 1;
if (parse_compression_type(algo, &config->comp_type) != 0) {
fprintf(stderr, "未知的压缩算法: %s\n", algo);
return -1;
}
}
break;
case 'h':
print_help(argv[0]);
exit(0);
default:
// 检查数字参数 (压缩级别)
if (optopt == 0 && optarg && optarg[0] == '-') {
char *p = optarg + 1;
if (*p >= '0' && *p <= '9') {
config->compression_level = atoi(p);
if (config->compression_level < 1) config->compression_level = 1;
if (config->compression_level > 22) config->compression_level = 22;
break;
}
}
fprintf(stderr, "未知选项: -%c\n", opt);
return -1;
}
}
// 检查 -notop 参数
for (int i = optind; i < argc; i++) {
if (strcmp(argv[i], "-notop") == 0) {
config->no_top_dir = 1;
// 移除这个参数
for (int j = i; j < argc - 1; j++) {
argv[j] = argv[j + 1];
}
argc--;
break;
}
}
// 获取输入模式
if (optind < argc) {
config->pattern_count = argc - optind;
config->input_patterns = malloc(config->pattern_count * sizeof(char *));
for (int i = 0; i < config->pattern_count; i++) {
config->input_patterns[i] = strdup(argv[optind + i]);
}
} else {
fprintf(stderr, "错误: 需要指定输入文件或模式\n");
return -1;
}
return 0;
}
// 在原有代码基础上添加以下结构和函数
// 文件信息结构体
typedef struct {
char *path; // 文件完整路径
char *archive_path; // 在压缩包中的路径(处理后)
struct stat file_stat; // 文件状态信息
} FileEntry;
// 目录信息结构体
typedef struct {
char *path; // 目录完整路径
char *archive_path; // 在压缩包中的路径(处理后)
int is_empty; // 是否为空目录
} DirEntry;
DirEntry *dir_list = NULL;
size_t dir_count = 0;
size_t dir_capacity = 0;
// 检查目录是否为空
int is_directory_empty(const char *path) {
DIR *dir = opendir(path);
if (!dir) return 0;
struct dirent *entry;
int item_count = 0;
while ((entry = readdir(dir)) != NULL) {
if (strcmp(entry->d_name, ".") != 0 && strcmp(entry->d_name, "..") != 0) {
item_count++;
}
}
closedir(dir);
return (item_count == 0) ? 1 : 0;
}
// 递归收集所有文件和目录信息
void collect_all_items(const char *base_path, const char *current_path) {
char full_path[1024];
if (strlen(current_path) > 0) {
snprintf(full_path, sizeof(full_path), "%s%c%s", base_path, PATH_SEPARATOR, current_path);
} else {
snprintf(full_path, sizeof(full_path), "%s", base_path);
}
DIR *dir = opendir(full_path);
if (!dir) return;
struct dirent *entry;
while ((entry = readdir(dir)) != NULL) {
if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) {
continue;
}
char new_path[1024];
if (strlen(current_path) > 0) {
snprintf(new_path, sizeof(new_path), "%s%c%s", current_path, PATH_SEPARATOR, entry->d_name);
} else {
snprintf(new_path, sizeof(new_path), "%s", entry->d_name);
}
char absolute_path[1024];
snprintf(absolute_path, sizeof(absolute_path), "%s%c%s", full_path, PATH_SEPARATOR, entry->d_name);
struct stat st;
if (stat(absolute_path, &st) == 0) {
if (S_ISDIR(st.st_mode)) {
// 添加到目录列表
if (dir_count >= dir_capacity) {
dir_capacity = dir_capacity ? dir_capacity * 2 : 64;
dir_list = realloc(dir_list, dir_capacity * sizeof(DirEntry));
}
dir_list[dir_count].path = strdup(absolute_path);
dir_list[dir_count].is_empty = is_directory_empty(absolute_path);
dir_list[dir_count].archive_path = NULL; // 稍后处理
dir_count++;
// 递归处理子目录
collect_all_items(base_path, new_path);
} else {
// 添加到文件列表
if (file_count >= file_capacity) {
file_capacity = file_capacity ? file_capacity * 2 : 64;
file_list = realloc(file_list, file_capacity * sizeof(FileEntry));
}
file_list[file_count].path = strdup(absolute_path);
file_list[file_count].archive_path = NULL; // 稍后处理
file_list[file_count].file_stat = st;
file_count++;
}
}
}
closedir(dir);
}
// 统一处理所有路径的前缀
void process_archive_paths(Config *config) {
// 处理文件路径
for (size_t i = 0; i < file_count; i++) {
// 找到相对于base_path的路径
const char *base_path = config->input_patterns[0];
const char *full_path = file_list[i].path;
// 安全检查:确保 base_path 不是完整路径的一部分
if (strlen(full_path) == strlen(base_path) && strcmp(full_path, base_path) == 0) {
// 单个文件匹配模式
file_list[i].archive_path = strdup(basename((char *)full_path));
continue;
}
// 检查是否以base_path开头
if (strncmp(full_path, base_path, strlen(base_path)) == 0) {
const char *relative_path = full_path + strlen(base_path);
// 移除开头的路径分隔符
if (*relative_path == PATH_SEPARATOR) {
relative_path++;
}
if (config->no_top_dir) {
// 移除顶级目录:找到第一个路径分隔符
char *first_sep = strchr(relative_path, PATH_SEPARATOR);
if (first_sep) {
file_list[i].archive_path = strdup(first_sep + 1);
} else {
file_list[i].archive_path = strdup(relative_path);
}
} else {
file_list[i].archive_path = strdup(relative_path);
}
} else {
// 如果不以base_path开头,使用完整路径
file_list[i].archive_path = strdup(full_path);
}
}
// 处理目录路径(只处理空目录)
for (size_t i = 0; i < dir_count; i++) {
if (dir_list[i].is_empty) {
const char *base_path = config->input_patterns[0];
const char *full_path = dir_list[i].path;
if (strncmp(full_path, base_path, strlen(base_path)) == 0) {
const char *relative_path = full_path + strlen(base_path);
if (*relative_path == PATH_SEPARATOR) {
relative_path++;
}
if (config->no_top_dir) {
char *first_sep = strchr(relative_path, PATH_SEPARATOR);
if (first_sep) {
char *archive_path = malloc(strlen(first_sep + 1) + 2);
strcpy(archive_path, first_sep + 1);
strcat(archive_path, "/");
dir_list[i].archive_path = archive_path;
} else {
char *archive_path = malloc(strlen(relative_path) + 2);
strcpy(archive_path, relative_path);
strcat(archive_path, "/");
dir_list[i].archive_path = archive_path;
}
} else {
char *archive_path = malloc(strlen(relative_path) + 2);
strcpy(archive_path, relative_path);
strcat(archive_path, "/");
dir_list[i].archive_path = archive_path;
}
}
}
}
}
// 修改 compress_files 函数
int compress_files(Config *config) {
// 清空之前的列表
file_count = 0;
dir_count = 0;
// 收集所有文件和目录信息
for (int i = 0; i < config->pattern_count; i++) {
struct stat st;
if (stat(config->input_patterns[i], &st) == 0) {
if (S_ISDIR(st.st_mode)) {
collect_all_items(config->input_patterns[i], "");
} else {
// 单个文件
if (file_count >= file_capacity) {
file_capacity = file_capacity ? file_capacity * 2 : 64;
file_list = realloc(file_list, file_capacity * sizeof(FileEntry));
}
file_list[file_count].path = strdup(config->input_patterns[i]);
file_list[file_count].archive_path = NULL;
file_list[file_count].file_stat = st;
file_count++;
}
}
}
// 统一处理所有路径
process_archive_paths(config);
printf("找到 %zu 个文件和 %zu 个空目录\n", file_count, dir_count);
// 输出文件列表
printf("\n文件列表:\n");
for (size_t i = 0; i < file_count; i++) {
printf(" %s -> %s\n", file_list[i].path, file_list[i].archive_path);
}
// 输出空目录列表
printf("\n空目录列表:\n");
for (size_t i = 0; i < dir_count; i++) {
if (dir_list[i].is_empty && dir_list[i].archive_path) {
printf(" %s -> %s\n", dir_list[i].path, dir_list[i].archive_path);
}
}
printf("\n");
// 创建压缩包并添加文件和空目录...
// [原有的压缩逻辑,分别处理文件和目录]
// 创建压缩包
int err;
struct zip *archive = zip_open(config->output_file, ZIP_CREATE | ZIP_TRUNCATE, &err);
if (!archive) {
zip_error_t error;
zip_error_init_with_code(&error, err);
fprintf(stderr, "无法创建压缩包: %s\n", zip_error_strerror(&error));
zip_error_fini(&error);
return -1;
}
// 在 compress_files 函数中修改文件添加逻辑
for (size_t i = 0; i < file_count; i++) {
// 检查是否是目录(通过路径结尾的 / 判断)
int is_directory = (file_list[i].archive_path[strlen(file_list[i].archive_path) - 1] == '/');
if (is_directory) {
// 处理目录:创建目录条目
zip_int64_t index = zip_dir_add(archive, file_list[i].archive_path, ZIP_FL_ENC_UTF_8);
if (index >= 0) {
printf("已添加目录: %s -> %s\n", file_list[i].path, file_list[i].archive_path);
} else {
fprintf(stderr, "添加目录失败: %s\n", file_list[i].archive_path);
}
} else {
// 处理文件:正常添加文件内容
FILE *fp = fopen(file_list[i].path, "rb");
if (!fp) {
fprintf(stderr, "无法打开文件: %s\n", file_list[i].path);
continue;
}
fseek(fp, 0, SEEK_END);
long size = ftell(fp);
fseek(fp, 0, SEEK_SET);
char *buffer = malloc(size);
fread(buffer, 1, size, fp);
fclose(fp);
struct zip_source *source = zip_source_buffer(archive, buffer, size, 1);
if (source) {
zip_int64_t index = zip_file_add(archive, file_list[i].archive_path, source, ZIP_FL_ENC_UTF_8);
if (index >= 0) {
zip_uint16_t method = get_compression_method(config->comp_type);
zip_set_file_compression(archive, index, method, config->compression_level);
printf("已添加文件: %s -> %s\n", file_list[i].path, file_list[i].archive_path);
} else {
zip_source_free(source);
fprintf(stderr, "添加文件失败: %s\n", file_list[i].archive_path);
}
} else {
free(buffer);
fprintf(stderr, "创建源失败: %s\n", file_list[i].archive_path);
}
}
}
// !!!在这里添加空目录的代码!!!
for (size_t i = 0; i < dir_count; i++) {
if (dir_list[i].is_empty && dir_list[i].archive_path) {
zip_int64_t index = zip_dir_add(archive, dir_list[i].archive_path, ZIP_FL_ENC_UTF_8);
if (index >= 0) {
printf("已添加空目录: %s -> %s\n", dir_list[i].path, dir_list[i].archive_path);
} else {
fprintf(stderr, "添加空目录失败: %s\n", dir_list[i].archive_path);
}
}
}
// 关闭压缩包
if (zip_close(archive) != 0) {
fprintf(stderr, "关闭压缩包失败: %s\n", zip_strerror(archive));
return -1;
}
printf("压缩完成: %s (%zu 个文件, %s)\n",
config->output_file, file_count, get_compression_name(config->comp_type));
return 0;
}
// 解压文件(简化版)
int decompress_files(Config *config) {
// 解压逻辑保持不变(省略以节省空间)
printf("解压功能暂未实现\n");
return -1;
}
int main(int argc, char *argv[]) {
Config config;
init_config(&config);
if (parse_arguments(argc, argv, &config) != 0) {
free_config(&config);
return 1;
}
int result;
if (config.compress) {
result = compress_files(&config);
} else {
result = decompress_files(&config);
}
// 清理资源
free_config(&config);
if (file_list) {
for (size_t i = 0; i < file_count; i++) {
free(file_list[i].path);
free(file_list[i].archive_path);
}
free(file_list);
}
return result;
}
压缩文件示例
./zipm zipm*.c -o c_zipm.zip
找到 8 个文件和 0 个空目录
文件列表:
zipm2.c -> zipm2.c
zipm3.c -> zipm3.c
zipm4.c -> zipm4.c
zipm5.c -> zipm5.c
zipm6.c -> zipm6.c
zipm7.c -> zipm7.c
zipm8.c -> zipm8.c
zipm.c -> zipm.c
空目录列表:
已添加文件: zipm2.c -> zipm2.c
已添加文件: zipm3.c -> zipm3.c
已添加文件: zipm4.c -> zipm4.c
已添加文件: zipm5.c -> zipm5.c
已添加文件: zipm6.c -> zipm6.c
已添加文件: zipm7.c -> zipm7.c
已添加文件: zipm8.c -> zipm8.c
已添加文件: zipm.c -> zipm.c
压缩完成: c_zipm.zip (8 个文件, Zstandard)
递归压缩目录示例
./zipm target -r -o top5.zip
找到 9 个文件和 7 个空目录
文件列表:
target/[Content_Types].xml -> [Content_Types].xml
target/docProps/app.xml -> docProps/app.xml
target/docProps/core.xml -> docProps/core.xml
target/xl/theme/theme1.xml -> xl/theme/theme1.xml
target/xl/worksheets/sheet1.xml -> xl/worksheets/sheet1.xml
target/xl/styles.xml -> xl/styles.xml
target/xl/_rels/workbook.xml.rels -> xl/_rels/workbook.xml.rels
target/xl/workbook.xml -> xl/workbook.xml
target/_rels/.rels -> _rels/.rels
空目录列表:
target/_rels/emptydir -> _rels/emptydir/
已添加文件: target/[Content_Types].xml -> [Content_Types].xml
已添加文件: target/docProps/app.xml -> docProps/app.xml
已添加文件: target/docProps/core.xml -> docProps/core.xml
已添加文件: target/xl/theme/theme1.xml -> xl/theme/theme1.xml
已添加文件: target/xl/worksheets/sheet1.xml -> xl/worksheets/sheet1.xml
已添加文件: target/xl/styles.xml -> xl/styles.xml
已添加文件: target/xl/_rels/workbook.xml.rels -> xl/_rels/workbook.xml.rels
已添加文件: target/xl/workbook.xml -> xl/workbook.xml
已添加文件: target/_rels/.rels -> _rels/.rels
已添加空目录: target/_rels/emptydir -> _rels/emptydir/
压缩完成: top5.zip (9 个文件, Zstandard)


被折叠的 条评论
为什么被折叠?



