DeepSeek用C编写的支持Zstandard 压缩的 ZIP 工具
本意是想让它写一个能把一个目录下所有文件及子目录都压缩的工具就够了,又想,既然提要求了,干脆就多提几点,于是有了下面这个。
/*** zipzstd.c - 支持 Zstandard 压缩的 ZIP 工具* 支持参数: 通配符, 递归, 压缩级别, 线程数, 输出文件, 解压缩, 排除顶级目录*/#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <zip.h>
#include <dirent.h>
#include <sys/stat.h>
#include <fnmatch.h>
#include <pthread.h>
#include <errno.h>
#include <getopt.h>
#include <libgen.h>#ifdef _WIN32
#include <direct.h>
#define mkdir(path, mode) _mkdir(path)
#define PATH_SEPARATOR '\\'
#else
#include <unistd.h>
#include <libgen.h>
#define PATH_SEPARATOR '/'
#endif// 配置结构体
typedef struct {int compress; // 1=压缩, 0=解压int recursive; // 递归子目录int compression_level; // 压缩级别int thread_count; // 线程数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;
pthread_mutex_t file_mutex = PTHREAD_MUTEX_INITIALIZER;// 初始化配置
void init_config(Config *config) {config->compress = 1;config->recursive = 0;config->compression_level = 6;config->thread_count = 1;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);
}// 打印帮助信息
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(" -T数字 线程数 (默认:1)\n");printf(" -o文件 输出文件名\n");printf(" -notop 不包括顶级目录\n");printf(" -h 显示帮助信息\n");printf("\n示例:\n");printf(" %s *.txt *.c -r -9 -T4 -o archive.zip\n", program_name);printf(" %s -d archive.zip -o ./output\n", program_name);printf(" %s docs/* -r -notop -o docs.zip\n", program_name);
}// 解析命令行参数
int parse_arguments(int argc, char *argv[], Config *config) {static struct option long_options[] = {{"help", no_argument, 0, 'h'},{0, 0, 0, 0}};int opt;while ((opt = getopt_long(argc, argv, "drT:o:h", long_options, NULL)) != -1) {switch (opt) {case 'd':config->compress = 0;break;case 'r':config->recursive = 1;break;case 'T':config->thread_count = atoi(optarg);if (config->thread_count < 1) config->thread_count = 1;break;case 'o':config->output_file = strdup(optarg);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;
}// 检查文件是否匹配模式
int matches_pattern(const char *filename, Config *config) {for (int i = 0; i < config->pattern_count; i++) {if (fnmatch(config->input_patterns[i], filename, 0) == 0) {return 1;}}return 0;
}// 递归收集文件
void collect_files(const char *base_path, const char *current_path, Config *config) {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 (config->recursive) {collect_files(base_path, new_path, config);}} else {// 检查文件是否匹配模式if (matches_pattern(entry->d_name, config) || matches_pattern(new_path, config) ||config->pattern_count == 0) {pthread_mutex_lock(&file_mutex);if (file_count >= file_capacity) {file_capacity = file_capacity ? file_capacity * 2 : 64;file_list = realloc(file_list, file_capacity * sizeof(FileInfo));}file_list[file_count].path = strdup(absolute_path);// 处理压缩包中的路径if (config->no_top_dir) {// 移除顶级目录部分char *first_sep = strchr(new_path, PATH_SEPARATOR);if (first_sep) {file_list[file_count].archive_path = strdup(first_sep + 1);} else {file_list[file_count].archive_path = strdup(new_path);}} else {file_list[file_count].archive_path = strdup(new_path);}file_list[file_count].file_stat = st;file_count++;pthread_mutex_unlock(&file_mutex);}}}}closedir(dir);
}// 压缩文件线程函数
void *compress_thread(void *arg) {struct zip *archive = (struct zip *)arg;while (1) {pthread_mutex_lock(&file_mutex);static size_t current_index = 0;if (current_index >= file_count) {pthread_mutex_unlock(&file_mutex);break;}FileInfo file = file_list[current_index++];pthread_mutex_unlock(&file_mutex);// 读取文件内容FILE *fp = fopen(file.path, "rb");if (!fp) {fprintf(stderr, "无法打开文件: %s\n", file.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.archive_path, source, ZIP_FL_ENC_UTF_8);if (index >= 0) {zip_set_file_compression(archive, index, ZIP_CM_ZSTD, 6);printf("已添加: %s -> %s\n", file.path, file.archive_path);} else {zip_source_free(source);fprintf(stderr, "添加文件失败: %s\n", file.archive_path);}} else {free(buffer);fprintf(stderr, "创建源失败: %s\n", file.archive_path);}free(file.path);free(file.archive_path);}return NULL;
}// 压缩文件
int compress_files(Config *config) {if (!config->output_file) {config->output_file = strdup("output.zip");}// 收集文件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_files(config->input_patterns[i], "", config);} else {// 单个文件pthread_mutex_lock(&file_mutex);if (file_count >= file_capacity) {file_capacity = file_capacity ? file_capacity * 2 : 64;file_list = realloc(file_list, file_capacity * sizeof(FileInfo));}file_list[file_count].path = strdup(config->input_patterns[i]);if (config->no_top_dir) {char *base = basename(config->input_patterns[i]);file_list[file_count].archive_path = strdup(base);} else {file_list[file_count].archive_path = strdup(config->input_patterns[i]);}file_list[file_count].file_stat = st;file_count++;pthread_mutex_unlock(&file_mutex);}}}if (file_count == 0) {fprintf(stderr, "错误: 没有找到匹配的文件\n");return -1;}printf("找到 %zu 个文件进行压缩\n", file_count);// 创建压缩包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;}// 创建压缩线程pthread_t *threads = malloc(config->thread_count * sizeof(pthread_t));for (int i = 0; i < config->thread_count; i++) {pthread_create(&threads[i], NULL, compress_thread, archive);}// 等待所有线程完成for (int i = 0; i < config->thread_count; i++) {pthread_join(threads[i], NULL);}free(threads);// 关闭压缩包if (zip_close(archive) != 0) {fprintf(stderr, "关闭压缩包失败: %s\n", zip_strerror(archive));return -1;}printf("压缩完成: %s (%zu 个文件)\n", config->output_file, file_count);return 0;
}// 解压文件
int decompress_files(Config *config) {if (!config->input_patterns || config->pattern_count == 0) {fprintf(stderr, "错误: 需要指定要解压的ZIP文件\n");return -1;}const char *zip_file = config->input_patterns[0];const char *output_dir = config->output_file ? config->output_file : ".";// 创建输出目录struct stat st;if (stat(output_dir, &st) != 0) {if (mkdir(output_dir, 0755) != 0) {fprintf(stderr, "无法创建输出目录: %s\n", output_dir);return -1;}}// 打开ZIP文件int err;struct zip *archive = zip_open(zip_file, 0, &err);if (!archive) {zip_error_t error;zip_error_init_with_code(&error, err);fprintf(stderr, "无法打开ZIP文件: %s\n", zip_error_strerror(&error));zip_error_fini(&error);return -1;}zip_int64_t num_entries = zip_get_num_entries(archive, 0);printf("开始解压 %s (%ld 个文件) 到 %s\n", zip_file, (long)num_entries, output_dir);// 解压所有文件for (zip_int64_t i = 0; i < num_entries; i++) {const char *name = zip_get_name(archive, i, 0);if (!name) continue;// 构建输出路径char output_path[1024];snprintf(output_path, sizeof(output_path), "%s%c%s", output_dir, PATH_SEPARATOR, name);// 创建目录char *dir_path = strdup(output_path);char *last_sep = strrchr(dir_path, PATH_SEPARATOR);if (last_sep) {*last_sep = '\0';struct stat dir_st;if (stat(dir_path, &dir_st) != 0) {mkdir(dir_path, 0755);}}free(dir_path);// 解压文件struct zip_file *zf = zip_fopen_index(archive, i, 0);if (zf) {FILE *outfile = fopen(output_path, "wb");if (outfile) {char buffer[8192];zip_int64_t nbytes;while ((nbytes = zip_fread(zf, buffer, sizeof(buffer))) > 0) {fwrite(buffer, 1, nbytes, outfile);}fclose(outfile);printf("解压: %s\n", output_path);}zip_fclose(zf);}}zip_close(archive);printf("解压完成: %s\n", output_dir);return 0;
}// 主函数
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;
}
编译
export LIBRARY_PATH=$LIBRARY_PATH:/par/libzip-1.11.4/build/lib
export C_INCLUDE_PATH=$C_INCLUDE_PATH:/par/libzip-1.11.4/lib
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/par/libzip-1.11.4/build/lib
gcc zipzstd2.c -o zipzstd -lzip
执行
./zipzstd
错误: 需要指定输入文件或模式
./zipzstd -h
用法: ./zipzstd [选项] <文件/模式...>
选项:-d 解压缩模式-r 递归处理子目录-数字 压缩级别 (1-22, 默认:6)-T数字 线程数 (默认:1)-o文件 输出文件名-notop 不包括顶级目录-h 显示帮助信息示例:./zipzstd *.txt *.c -r -9 -T4 -o archive.zip./zipzstd -d archive.zip -o ./output./zipzstd docs/* -r -notop -o docs.zip
假定有如下目录
cd test
ls
ParserInterfaceTest test_fdw_create.sql test_fdw_numericquery_solution.txt
datebool_test.xlsx test_fdw_create_solution.txt test_fdw_runall.sh
numeric_test.xlsx test_fdw_dateboolquery.sql test_fdw_stringquery.sql
sf0001 test_fdw_dateboolquery_solution.txt test_fdw_stringquery_solution.txt
string_test.xlsx test_fdw_numericquery.sql
把*.txt *.sql文件压到archive.zip
../zipzstd *.txt *.sql -o archive.zip
找到 8 个文件进行压缩
已添加: test_fdw_create_solution.txt -> test_fdw_create_solution.txt
已添加: test_fdw_dateboolquery_solution.txt -> test_fdw_dateboolquery_solution.txt
已添加: test_fdw_numericquery_solution.txt -> test_fdw_numericquery_solution.txt
已添加: test_fdw_stringquery_solution.txt -> test_fdw_stringquery_solution.txt
已添加: test_fdw_create.sql -> test_fdw_create.sql
已添加: test_fdw_dateboolquery.sql -> test_fdw_dateboolquery.sql
已添加: test_fdw_numericquery.sql -> test_fdw_numericquery.sql
已添加: test_fdw_stringquery.sql -> test_fdw_stringquery.sql
压缩完成: archive.zip (8 个文件)
再建立两个子目录,往两者各复制一个文件。想实验一下递归,结果没有把两个子目录压缩进去,还是刚才那些文件。
mkdir s
cp test_fdw_create.sql s
mkdir t
cp test_fdw_create_solution.txt t
../zipzstd *.txt *.sql -r -o archiver.zip
找到 8 个文件进行压缩压缩完成: archiver.zip (8 个文件)
试验解压缩,成功。
../zipzstd -d archiver.zip -o 3
开始解压 archiver.zip (8 个文件) 到 3
解压: 3/test_fdw_create_solution.txt
解压: 3/test_fdw_dateboolquery_solution.txt
解压: 3/test_fdw_numericquery_solution.txt
解压: 3/test_fdw_stringquery_solution.txt
解压: 3/test_fdw_create.sql
解压: 3/test_fdw_dateboolquery.sql
解压: 3/test_fdw_numericquery.sql
解压: 3/test_fdw_stringquery.sql
解压完成: 3
针对刚才未达预期的命令询问DeepSeek,他给出了如下正确的写法:
../zipzstd ./*.txt ./*.sql ./**/*.txt ./**/*.sql -r -o archiver3.zip
找到 18 个文件进行压缩
已添加: ./test_fdw_create_solution.txt -> ./test_fdw_create_solution.txt
已添加: ./test_fdw_dateboolquery_solution.txt -> ./test_fdw_dateboolquery_solution.txt
已添加: ./test_fdw_numericquery_solution.txt -> ./test_fdw_numericquery_solution.txt
已添加: ./test_fdw_stringquery_solution.txt -> ./test_fdw_stringquery_solution.txt
已添加: ./test_fdw_create.sql -> ./test_fdw_create.sql
已添加: ./test_fdw_dateboolquery.sql -> ./test_fdw_dateboolquery.sql
已添加: ./test_fdw_numericquery.sql -> ./test_fdw_numericquery.sql
已添加: ./test_fdw_stringquery.sql -> ./test_fdw_stringquery.sql
已添加: ./3/test_fdw_create_solution.txt -> ./3/test_fdw_create_solution.txt
已添加: ./3/test_fdw_dateboolquery_solution.txt -> ./3/test_fdw_dateboolquery_solution.txt
已添加: ./3/test_fdw_numericquery_solution.txt -> ./3/test_fdw_numericquery_solution.txt
已添加: ./3/test_fdw_stringquery_solution.txt -> ./3/test_fdw_stringquery_solution.txt
已添加: ./t/test_fdw_create_solution.txt -> ./t/test_fdw_create_solution.txt <------------
已添加: ./3/test_fdw_create.sql -> ./3/test_fdw_create.sql
已添加: ./3/test_fdw_dateboolquery.sql -> ./3/test_fdw_dateboolquery.sql
已添加: ./3/test_fdw_numericquery.sql -> ./3/test_fdw_numericquery.sql
已添加: ./3/test_fdw_stringquery.sql -> ./3/test_fdw_stringquery.sql
已添加: ./s/test_fdw_create.sql -> ./s/test_fdw_create.sql <------------
压缩完成: archiver3.zip (18 个文件)
可见它递归压缩了t、s,以及刚才解压缩生成的3目录。
我估计他做过类似的项目,刚才的代码除了一处宏定义缺一个下划线,一字未改,就能实现基本的功能,当然还有问题,但已经很不错了。