在DOS系统下实现递归删除旧代码在Windows移植实现过程及经验总结

[作业任务]在DOS系统下实现递归删除旧代码在Windows移植实现过程及经验总结

环境准备

  1. 编译环境:CMake
  2. 系统环境:Arch Linux x86 (Kernel Linux 6.18.2-arch2-1)
  3. 编辑器: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仓库:点击跳转

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值