shell项目简报

shell项目简报

一 项目目的

shell作为命令语言,shell项目的目的在于交互式地解释和执行用户输入的命令。

二 项目需求分析

项目主要分为两大部分:

第一部分: 命令的获取分析

命令解析器功能在于接受并解析用户输入的命令,运行方式为死循环模式,直至用户输入exit之后退出。运行过程主要由四步实现。

1.参考于linux运行界面,输出提示符。

2.接受用户输入的命令。

3.对用户输入的命令进行简单的解析

4.利用fork()函数创建出子进程,在子进程中用execv()函数,根据对命令分析的结果,将切割得到的命令以及参数传入,实现程序进行程序替换,完成命令的实现,该过程中,父进程调用waitpid()等待子进程的退出,然后进入下一次循环。

第二部分 :各命令的实现

三 实现过程

(一)命令的获取分析

1 命令提示符

通过我们对linux的命令提示符进行分析,我们发现它由三部分组成:@符号前面为用户名,后面是网络上的名称,空格之后是当前工作目录的路径,对于这三部分如何获取,是我们需要解决的主要的问题。

(1).用户名

用户名存放于结构体passwd中,passwd结构在<pwd.h>中定义如下:

struct passwd

{

字符* pw_name; /*用户名 */

字符* pw_passwd; /*用户密码 */

uid_t pw_uid; /*用户身份 */ gid_t pw_gid; / *组ID * /

字符* pw_gecos; /*真正的名字 */

字符* pw_dir; / *主目录* /

字符* pw_shell; / * shell程序* /

};

我们可以利用getpwuid函数是通过用户的UID查找用户的passwd,然后访问pw_name获取用户名.。

(2).网络名称

网络名称记录在结构体utsname中,ustname原型:

struct utsname  {

char sysname[_UTSNAME_SYSNAME_LENGTH];//当前操作系统名  

char nodename[_UTSNAME_NODENAME_LENGTH];//网络上的名称  

char release[_UTSNAME_RELEASE_LENGTH];//当前发布级别  

char version[_UTSNAME_VERSION_LENGTH];//当前发布版本  

char machine[_UTSNAME_MACHINE_LENGTH];//当前硬件体系类型

#if _UTSNAME_DOMAIN_LENGTH - 0  /* Name of the domain of this node on the network.  */

# ifdef __USE_GNU  char domainname[_UTSNAME_DOMAIN_LENGTH]; //当前域名

# else  char __domainname[_UTSNAME_DOMAIN_LENGTH];

# endif #endif

 };

因此我们只要访问utsname.nodename就可以获取到网络名称,但是我们界面输出的网络名称其实是我们访问的一部分,因此我们要对访问到的数组进行剪裁处理。

(3).当前工作路径

当前工作路径的获取我们可以调用getcwd()函数来实现,其函数原型为char *getcwd( char *buffer, int maxlen );作用是将工作路径存储于buffer中,但是我们需要的是这一级的目录名,所以在获取到工作目录之后,我们需要对获取到的工作目录进行切割,只保留当前级的名称。

接下来我们要完成的工作主要是对命令的剖析,这个过程中我们遇到这样几个问题: 1.对于用户输入的一串命令的切割与分析 在这个切割用户命令时,我们调用strtok()函数根据" "为标志,将命令切割为若干部分,我们根据第一部分来决定后面fork()中的execv()函数将要拿哪个程序替换,我们另外在别的目录写的各个命令的实现其可执行文件名一定要与命令相对应。 2.对于替换程序的路径的处理

对于这个问题,刚开始想到的是直接用户命令中第一个空格之前字符获取出来字符串调用同名的可执行文件文件进行替换就好,但是遇到了这样的问题:如果替换程序的可执行文件和我们的bsah.c文件没有在同一个文件夹,那我们直接调用自然会失败,那么如何让解决这个问题呢,事实上我们只需要对替换程序的路径上做稍微改动就好,我们分两种情况来考虑:

第一种,指定了可执行文件的路径,这种情况下我们只需要在fork()子进程中根据提供的指定的路径去访问就好,第二种,只有命令,那么我们首先在命令实现的时候就将所有的命令实现代码放在同一个目录底下,我们在execv()之前只需要将我们获取到的命令利用strcat()函数链接到我们给定的存放代码的目录名的绝对路径后面,这样就可以利用组合好的绝对路径去执行我们已经生成好的可执行文件,哪怕是换了用户登录可以执行在先前用户的代码。

2 命令的获取

命令的获取我们用到了fgets()函数,命令必须带参数,取得的字符串包括最后输入的换行符,故要去掉命令字符串末尾的“/n”,变成“/0”。

3 命令的切割

用字符串切割函数strtok()将得到的命令行以空格切割存储在字符串数组中。

(二)各个命令的实现

1.exit

退出命令,我们直接在主函数中对命令进行判断如果用户输入exit,我们直接在主函数利用exit()结束进程。

2.pwd

pwd命来用开打印当前工作目录,这一过程的实现其实我们在之前已经接触过了,我们只需要调用getcwd()函数或得到当前的工作目录然后输出就好。

3.cd

cd是linux操作系统的基础命令,作用是改变当前工作目录,对于cd命令的实现我们需要用到chdir()函数,其函数原型为int chdir(const char *path),函数主要功能为用于改变当前工作目录,其参数为path 记录目的目录路径,可以是绝对路径或相对路径。通过此函数我们就可以切换到指定的工作目录,目的目录路径由主函数对输入命令切割后传参过来,但是我们知道cd在改变当前工作目录时除了使用cd加要切换的目录的相对路径或者绝对路径以外,cd 还有别的命令比如cd ~,cd -,cd..。所以对于这些命令的实现我们需要另外实现。

cd ~:将工作目录切为到主目录,查阅资料我们得知在结构体passwd(此结构体在前面有提及)中的pe_dir中记录了用户主目录信息,因此我们首先需要调用getpwuid()函数来获取passwd里面的pe_dir记录的主目录信息,函数原型为struct passwd *getpwuid(uid_t uid)参数为用户的UID,我们可以通过getuid()函数获取,之后再用chdir()函数完成工作目录的切换。

cd -:将工作目录切换到前一个工作目录,对于这个问题的实现,-我们只需要在实现过程中开辟一个空间用来记录上一次输入cd命令时所使用的目录内容,当我们对cd 后面的命令进行解析遇到“-”符号,则将记录前一个工作目录的内容的变量赋值给我们的path,然后调用chdir()函数来完成工作目录的切换。。

cd ..:将工作目录切换到上一级的工作目录,对于这条命令的实现,其实很简单,我们最主要的任务是读取上一级工作目录的路径,那么我们就可以用getcwd()先获取到当前的工作目录,然后对于从后面起遇到的第一个符号“/”之后的部分删除掉,就是上一级的工作目录,然后传参给chdir(),就可以切换到上一级工作目录。

4 cp

cp命令的功能主要是用来将一个或多个源文件或者目录复制到指定的目的文件或目录。它可以将单个源文件复制成一个指定文件名的具体的文件或一个已经存在的目录下。cp命令还支持同时复制多个文件,当一次复制多个文件时,目标文件参数必须是一个已经存在的目录,否则将出现错误。

(1)单个文件的复制:

单个文件的复制相对来说比较简单,我们分为两种情况,一种是复制的目的路径为具体文件路径,我们只需要对被复制的文件有读权限,复制的目的文件有写权限以及创建权限,在进行复制过程过程中,我们中需要开辟一个字符串空间作为被复制文件和目的文件资源复制的缓存区,即将被复制的文件按一定的大小读入字符串,然后将字符串里的数据复制到目的文件,循环此操作直到数据复制完成。而对于无论是被复制文件还是目的文件的路径的获取在mybash之中已经处理过了,我们只需要对它进行读取就可以直接使用了。

而另外一中就是将文件复制到指定目录底下,这种情况下,我们得到的复制路径并不完整,我们需要在复制之前对目的路径进行处理,首先我们对源文件从后面进行遍历,如果遇见'/'那么'/'之后的就是目的文件的文件名,如果没有遇到'/',那么整个源文件的路径名就是目的文件的文件名,之后我们再将或得到的目的文件的文件名与传进来的目录路径进行合并,然后构造出完整的目的路径,再进行上面的文件到文件的复制。

(2)目录(文件夹)的复制

目录的复制相对来说比较复杂,首先对目的目录进行判断,如果目录不存在,我们需要先进行目录的创建,之后我们需要多目录进行真正的拷贝工作,这个时候我们需要一个目录指针和读取目录返回的结构体,来记录相关的信息,之后我们就要用递归函数进行目录一层一层的创建,如果遇到目录就调用递归函数进行目录创建,如果遇到文件的话就直接调用我们之前写好的文件复制函数,直到递归结束。

这个过程中自己实现这样几个函数:

int Exist(char* path); //判断文件/目录是否存在
int IsFolder(char* path); //判断是否为目录
int FileCopy(char* argv[1], char* argv[2]); //复制文件
int FileCopyToFolder(char* argv[1], char* argv[2]); //复制文件到目录
int CreateFolder(char *folder); //创建目录
int FolderCopy(char* argv[1], char* argv[2]); //复制目录到目录

在这一个过程中我们遇到的第一个问题:目录的创建,我们首先要来判断我们的目的目录是否存在,如果不存在我们就要将其创建出来。

目录创建我们需要用到mkdir()函数来实现,首先我们来看一下mkdir()函数的原型:

int mkdir(const char *pathname, mode_t mode)作用是:以mode方式创建一个以参数pathname命名的目录,mode定义新创建目录的权限,若目录创建成功,则返回0;否则返回-1,并将错误记录到全局变量errno中。

mode方式有下面罗列的供大家参考: 

S_IRWXU 00700权限,代表该文件所有者拥有读,写和执行操作的权限
S_IRUSR(S_IREAD) 00400权限,代表该文件所有者拥有可读的权限
S_IWUSR(S_IWRITE) 00200权限,代表该文件所有者拥有可写的权限
S_IXUSR(S_IEXEC) 00100权限,代表该文件所有者拥有执行的权限
S_IRWXG 00070权限,代表该文件用户组拥有读,写和执行操作的权限
S_IRGRP 00040权限,代表该文件用户组拥有可读的权限
S_IWGRP 00020权限,代表该文件用户组拥有可写的权限
S_IXGRP 00010权限,代表该文件用户组拥有执行的权限
S_IRWXO 00007权限,代表其他用户拥有读,写和执行操作的权限
S_IROTH 00004权限,代表其他用户拥有可读的权限
S_IWOTH 00002权限,代表其他用户拥有可写的权限
S_IXOTH 00001权限,代表其他用户拥有执行的权限

在这里目录的创建我们需要一级一级的循环创建,那么我们应该要怎样处理已有的数据实现这一任务呢?我们可以采用这样的方法,对于我们的目的目录,从第二个字符开始进行判断,遇见'/'将它替换为结束符,然后以新生成的字符串作为作为目录名创建该目录,创建成功后,再将结束符换'/'进行下一级目录的创建,直到遍历结束。

当目的目录处理完之后,我们的目录复制具体怎么实现的呢?

首先我们需要一个DIR指针指向我们的源目录,一个dirent的指针指向我们的dirent结构体,该结构体用于记录文件夹目录内容,该结构体原型为:

struct dirent

{

long d_ino; /* inode number 索引节点号 */

off_t d_off; /* offset to this dirent 在目录文件中的偏移 */

unsigned short d_reclen; /* length of this d_name 文件名长 */

unsigned char d_type; /* the type of d_name 文件类型 */

char d_name [NAME_MAX+1]; /* file name (null-terminated) 文件名,最长256字符 */

}

我们需要用到的是里面的d_name,用来获取文件名。

接下来我们就要循环读取目录下的条目信息,将'/'和d_name连接到给的目的路径后面合成新的的目的路径名,进行递归调用进行目录拷贝和文件拷贝,这个过程中我们要对两个特殊目录处理一下,分别是.和..文件夹,因为Linux在所有文件夹都有一个.文件夹用于连接上一级目录,必须剔除,否则进行递归的话,后果很严重。整个循环过程的约束我们用到了readdir()函数,该函数成功则返回下个目录进入点. 有错误发生或读取到目录文件尾则返回NULL.

猜你喜欢

转载自blog.csdn.net/pretysunshine/article/details/83514858
今日推荐