[作业任务]在DOS系统下实现递归删除旧代码在Windows移植实现过程及经验总结
- 本文只对于递归删除的逻辑实现部分进行移植改写,并不对原有删除代码进行重构
环境准备
- 编译环境:CMake
- 系统环境:Arch Linux x86 (Kernel Linux 6.18.2-arch2-1)
- 编辑器:JetBrains CLion(采用C11语言标准)
以上三条均可按照个人的使用情况进行适当更换,不必完全相同
一、Git环境配置
在进行项目开发时,使用Git进行代码管理如今看来已经是一个理所当然的事情。因此在正式进行开发之前,我们应该进行Git的相关配置。
安装Git
Windows系统:winget install git.git
Linux系统:
一般而言,主流发行版的Linux系统官方仓库都支持提供Git的安装方式,可以根据自己使用的发行版进行调整。
- 若使用Arch/Manjaro:
sudo pacman -S git - 若使用Debian/Ubuntu:
sudo apt install git
上述方式若因为网络问题无法下载,可以参考清华大学开源镜像站的方法进行系统配置。
环境变量配置
Windows系统可以通过 设置-系统-设备信息-高级系统设置-环境变量-Path-添加 进行Git二进制可运行文件的配置(一般位于C:\ProgramFiles\Git\cmd),而Linux默认安装Git后自动完成配置。
仓库配置
首先在Gitee创建一个账号并设置好账户名,绑定邮箱,并创建一个空仓库。
我在这里直接随便放一个账户作为例子:
- 账号:WangXiang
- 密码:WangXiang123
- 邮箱:WX66@hotmail.com
- 仓库:WXWork
在Shell中输入下面两行指令确认本机昵称邮箱:
git config --global user.name "WangXiang"
git config --global user.email "WX66@hotmail.com"
创建好用户之后,我们要前往对应目录创建仓库:
cd /path #将path替换为你的文件夹路径
git init #初始化本地仓库
git remote add origin "https://gitee.com/WangXiang/WXWork"
git pull origin master -rebase #用于与远程仓库同步,这里使用Rebase方式拉取
紧接着将我们的代码上传到本地的.gti仓库中准备打包上传
git add . #添加本地文件夹中所有内容
git commit -m "在这里插入上传描述(不必须是英文)"
git push -u origin master #上传内容
如果不出问题的话,你已经完成了一次上传内容,恭喜🎉!
二、源代码分析
对于DOS系统,它已经不再是今天流行的操作系统之一,因此不能以现代操作系统的思维去对原有代码进行分析。为简化分析流程,我使用了Claude AI帮我对代码进行了不修改原有内容的注释编辑:
#include <dos.h>
#include <stdio.h>
#include <stdlib.h>
#include <dir.h>
#include <alloc.h>
#include <string.h>
/*
说明: 这个程序用于在 MS-DOS 环境下递归删除指定目录中的所有文件和子目录,
最后删除指定目录本身。程序依赖于许多 MS-DOS 专用函数和中断调用,
因此只能在 Turbo C / Borland C 等 DOS 编译器或 DOSBox 环境中正确运行。
*/
void main(int argc, char **argv)
{
/* 前向声明:递归删除函数 */
void delete_tree(void);
/* 临时缓冲区,用来保存路径等 */
char buffer[128];
/* 用于 fnsplit 分解路径的各部分(驱动器、目录、文件名、扩展名) */
char drive[MAXDRIVE], directory[MAXDIR], filename[MAXFILE], ext[MAXEXT];
/* 参数检查:必须提供一个目录参数 */
if (argc < 2)
{
printf (" >> Syntax error\n"); /* 语法错误提示 */
exit(0);
}
/* 将用户传入的路径拆分为 drive/directory/filename/ext 四部分 */
fnsplit (argv[1], drive, directory, filename, ext);
/* 取得当前工作目录,保存在 buffer 中,以便稍后返回 */
getcwd (buffer, sizeof(buffer));
/*
如果用户没有在参数中指定盘符(drive[0] == 0),
则使用当前工作目录的盘符和目录信息拼出完整路径到 buffer。
注意:此处使用 drive[0] == NULL 来判断是否有盘符,这是老式 DOS 代码的写法。
*/
if (drive[0] == NULL)
{
fnsplit (buffer, drive, directory, filename, ext);
strcpy (buffer, directory);
strcat (buffer, filename);
strcat (buffer, ext);
}
else
{
/* 如果显式指定了盘符,程序不接受(按原作者意图) */
printf (" >> Do not specify drive letter\n");
exit (1);
}
/* 比较拼出的 buffer 路径与用户传入的路径,防止删除当前目录 */
if (strcmpi(buffer, argv[1]) == 0)
{
printf (" >> Cannot delete current directory\n");
exit (1);
}
/* 保存当前工作目录(再次)到 directory 变量,以便结束后恢复 */
getcwd (directory, 64);
/* 进入目标目录;若失败输出错误信息,否则开始递归删除 */
if (chdir (argv[1]))
printf (" >> Invalid directory %s\n", argv[1]);
else
delete_tree(); /* 递归删除目录内容 */
/* 返回原来的工作目录 */
chdir (directory);
/* 删除空目录(目标目录应已被清空) */
rmdir (argv[1]);
printf(" >> Delete Successful!\n");
printf(" Press any key to quit...");
getch(); /* 等待用户按键 */
return;
}
/*
以下为 MS-DOS 特定的数据结构与中断寄存器变量,
用于直接调用 BIOS/DOS 中断(int 21h/int 2Fh 等)。
这些在标准 ISO C 中是不存在的。
*/
union REGS inregs, outregs;
struct SREGS segs;
/*
递归删除函数:遍历当前目录下的所有条目,
- 如果是子目录(属性 & 16)且不是 '.' 或 '..',则进入子目录递归删除后 rmdir
- 否则如果是文件则调用 remove() 删除
注意:使用了 findfirst/findnext(Borland/Turbo C 提供)来枚举目录
*/
void delete_tree(void)
{
struct ffblk fileinfo; /* findfirst/findnext 使用的结构 */
int result;
char far *farbuff; /* 远指针,用于 DOS 中的段:偏移操作(仅 DOS 编译器) */
unsigned dta_seg, dta_ofs; /* 保存 DTA(Disk Transfer Area)段和偏移 */
/* 找到第一个文件/目录,属性掩码 16 表示目录 */
result = findfirst("*.*", &fileinfo, 16);
/*
下面一段使用中断调用获取并保存当前 DTA(Disk Transfer Area)位置,
这是为了在递归切换目录时能恢复文件查找状态。
这些调用依赖于 DOS 中断(intdosx、segread 等),非标准。
*/
inregs.h.ah = 0x2f;
intdosx (&inregs, &outregs, &segs);
dta_seg = segs.es;
dta_ofs = outregs.x.bx;
/* 遍历目录项直到 findfirst/findnext 返回非零(无更多条目) */
while (! result)
{
/* 如果是目录且不是以 '.' 开头(排除 '.' 和 '..') */
if ((fileinfo.ff_attrib & 16) && (fileinfo.ff_name[0] != '.'))
{
/*
保存当前 DTA(通过中断)——这段代码是老式 DOS 技巧,
目的是在递归调用时能够恢复 findfirst/findnext 所使用的 DTA。
*/
inregs.h.ah = 0x1A;
inregs.x.dx = FP_SEG(farbuff);
segread(&segs);
intdosx (&inregs, &outregs, &segs);
/* 进入子目录,递归删除,然后回到上级 */
chdir (fileinfo.ff_name);
delete_tree();
chdir ("..");
/*
恢复之前保存的 DTA,并删除空子目录。
再次说明:这些操作为 DOS 特有,现代编译器/操作系统不支持。
*/
inregs.h.ah = 0x1A;
inregs.x.dx = dta_ofs;
segs.ds = dta_seg;
rmdir (fileinfo.ff_name);
}
else if (fileinfo.ff_name[0] != '.')
{
/* 非目录的普通文件,直接删除 */
remove (fileinfo.ff_name);
}
/* 查找下一个目录项 */
result = findnext (&fileinfo);
}
}
在这之中不难发现,递归删除的工作逻辑为 进入目录>>[查找目录>>进入二级目录]>>循环删除当前目录下的文件>>返回上一个目录>>删除目录 基于这个逻辑特性,我们即可进行下一步在Windows上进行代码移植
三、移植代码编写
首先我们要知道一个概念:如何在不知道目录情况的环境时正确的返回上一级目录
在Linux中,使用cd /PATH指令可以快速的移动到PATH所指的文件夹,此时使用cwd即可获取当前文件夹的地址路径信息(对应上方代码中的getcwd()函数),那么我们使用popen()管道传输system(“cwd”)函数的返回值就能轻松的获取到上一级目录的路径。
但是如果不使用新建pipe通信管道和system()指令获取,有没有更好的办法?
chdir()函数可以方便的进行工作目录的移动,那我们就可以使用chdir(..)的方法来快速移动到父系目录(这里的..代表的是../,也就是父系目录的路径,本目录的路径为./)
那么,将最难解决的问题解决之后我们就可以继续代码的编写:
#include "windows.h"
#include "stdio.h"
#include "string.h"
#include "dir.h"
/* 递归删除当前工作目录下的所有文件和子目录 */
static void delete_tree_in_cwd(void);
int main(int argc, char **argv)
{
/* 参数检查:需要一个目录参数 */
if (argc < 2)
{
printf(" >> Syntax error\n");
return 0;
}
/* 保持原有行为:如果参数以驱动器字母开头(如 C:),则拒绝 */
if (strlen(argv[1]) >= 2 && isalpha((unsigned char)argv[1][0]) && argv[1][1] == ':')
{
printf(" >> Do not specify drive letter\n");
return 1;
}
/* 切换到指定目录 */
if (chdir(argv[1]) != 0)
{
printf(" >> Cannot change directory to %s\n", argv[1]);
return 1;
}
/* 递归删除当前目录下的所有文件和子目录 */
delete_tree_in_cwd();
/* 回到上级目录并删除空目录 */
chdir("..");
rmdir(argv[1]);
return 0;
}
利用windows.h的方便系统函数操作,我们就完成了代码的编写。
四、在改写之后
DOS系统已经成为历史,在shell语言出现之后,原来复杂的文件删除操作也演变成了简单的一行rm -r /PATH。我们不需要继续进行如此繁杂的代码编写任务,人们对于计算机的学习也得以变得更加简单方便,更加深入。
但是我们今天为什么还需要继续研究这些看起来不常用,甚至可以说是“古老“的代码?
因为这些旧代码的运行逻辑值得我们去学习。
自计算机发明以来,人们就一直追求如何通过最少的步骤、最低的硬件消耗去完成高密度信息的计算,并以此发明了前所未有的海量算法。DOS系统的代码段就是如此精妙的工业艺术品,用逻辑代替算力实现复杂的计算任务。
这才是修复它们带给我们最宝贵的收获。
源码Gitee仓库:点击跳转

354

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



