C语言实现shell (包括管道输入输出重定向)

Posted 玛丽莲茼蒿

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C语言实现shell (包括管道输入输出重定向)相关的知识,希望对你有一定的参考价值。

一、实现的命令及功能

  • ls命令(实际上实现的是ls -l命令)
  • echo
  • cat
  • mkdir
  • rm
  • cd
  • pwd
  • cp
  • wc
  • rmdir
  • 输入输出重定向
  • 管道

二、分模块讲解

1.用到的所有头文件

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <pwd.h>
#include <grp.h>
#include<wait.h>
#include <time.h>  //解析文件的时间属性
#include <dirent.h> //打开目录,读目录,关目录

2.ls命令(实际上实现的是ls -l命令)

(1)实现功能

打印输出当前目录下的所有目录和文件的属性

(2)步骤

1)需要找到该路径下所有目录和文件的名字
2)找到当前路径,需要头文件#include<unistd.h>,调用函数getcwd():

char basePath[100];
getcwd(basePath,sizeof(basePath)); 

将得到的当前路径放在basePath里
3)在当前路径下找到所有文件、目录的名字
用到的头文件:#include <dirent.h>
用到的结构体:

DIR *dir;  //目录指针指向当前目录
struct dirent *ptr;  //循环指向每一个文件。

用opendir(绝对路径)函数打开当前目录,用readdir(目录指针dir)函数,循环读取每一个文件和目录的名字;最后用closedir(目录指针dir)函数关闭目录
4)调用stat函数,使用上一步得到的文件(目录)名作为参数,可以得到一系列的文件属性。
用到的头文件:#include <sys/types.h>、 #include <sys/stat.h> 、#include <unistd.h>
用到的结构体

struct stat info; //文件(目录)的属性信息

用到的函数:

stat(filename,&info);  //将文件filename的以下各种属性存放在info中

5)stat结构体种只包含了部分ls命令列出来的文件属性,如st_mode、st_size等,可以直接打印出来。例如,st_mode不需要解析。
6)文件的其他的属性需要调用更底层的函数进行进一步的解析。
• getpwuid函数:解码所有者信息(解析struct stat中的st_uid数据)

struct passwd *pswd;
pswd=getpwuid(info.st_uid);

• getgrgid函数:解码所属组信息(解析struct stat中的 st_gid数据)

struct  group  *grp;
grp=getgrgid(info.st_gid);

• localtime函数:解码上次访问时间信息 (解析struct stat中的 st_atime数据)

struct tm *atime;
atime = localtime(&info.st_mtime);

(3)完整代码

/*
实现ls命令
用法:ls
*/
void ls(){
    DIR *dir;
    struct dirent *ptr;
 //得到绝对路径
    char basePath[100];
    getcwd(basePath,sizeof(basePath));  //get当前路径 ,放在basePath里
    
    if ((dir=opendir(basePath)) == NULL)
    {
        perror("Open dir error...");
        exit(1);
    }
 
    while ((ptr=readdir(dir)) != NULL)
    {
        if(strcmp(ptr->d_name,".")==0 || strcmp(ptr->d_name,"..")==0)    ///current dir OR parrent dir
            continue;
        else if(ptr->d_type == 8)    ///file
            //printf("%s\\n",ptr->d_name);
            lsCore(ptr->d_name);
        else if(ptr->d_type == 10)    ///link file
            //printf("%s\\n",ptr->d_name);
            lsCore(ptr->d_name);
        else if(ptr->d_type == 4)    ///dir
        {
            //printf("%s\\n",ptr->d_name);
            lsCore(ptr->d_name);
        }
    }
    closedir(dir);
}

/*
本函数实现ls命令的核心功能;从ls函数中得到文件名,打印文件属性
*/
void lsCore(char *filename){
	struct stat info;
	stat(filename,&info);
	/*-----打印类型,权限----*/
	switch(info.st_mode & S_IFMT)
	{
		case S_IFREG:printf("-");break;
		case S_IFDIR:printf("d");break;
		case S_IFLNK:printf("l");break;
		case S_IFCHR:printf("c");break;
		case S_IFBLK:printf("b");break;
		case S_IFIFO:printf("p");break;
		case S_IFSOCK:printf("s");break;
	}
	char rwx[]={'r','w','x'};
	for(int i=0; i<10; i++)
	{
		printf("%c",info.st_mode & (0400>>i) ? rwx[i%3] : '-');
	}
	/*-----打印所有者 ----*/
	struct passwd *pswd;
	pswd=getpwuid(info.st_uid);
	printf(" %s",pswd->pw_name);
	/*-----所属组----*/
	struct  group  *grp;
	grp=getgrgid(info.st_gid);
	printf(" %s",grp->gr_name);
	
	/*-----打印文件大小----*/
	printf(" %ld ",info.st_size);
	/*-----打印最近时间 ----*/
	struct tm *atime;
	atime = localtime(&info.st_mtime);
	printf("%d-%d-%d %d:%d "
		,atime->tm_year+1900
		,atime->tm_mon+1,atime->tm_mday
		,atime->tm_hour,atime->tm_min);
	/*-----打印文件名----*/
	printf(" %s\\n",filename);
}

3.echo

/*
实现echo函数
实现功能:回显echo命令的参数
用法:echo hello dad!
*/
void echo(char *argv[]){
	int i=1;
	while(argv[i]!=NULL){
		printf("%s ",argv[i]);
		i++;
	}
	printf("\\n");
}

4.cat

/*
实现cat函数
实现功能:对文件1.txt内容进行标准输出
用法:cat 1.txt 或 cat /home/zxf/1.txt
*/
void cat(char *argv[]){
	char buf[200];  //模拟缓冲区
	int fp;
	fp=open(argv[1],O_RDONLY);
	if(fp==-1)
	{
		printf("open file failed\\n");
		return;
	}
	int n;
	while((n=read(fp,buf,199))>0)
		write(1,buf,n);  //1 标准
	close(fp);
}

5.mkdir

/*
实现mkdir函数
实现功能:根据绝对路径或相对路径创建新的目录
用法 :mkdir newdir或 mkdir /home/zxf/newdir
*/
void createDir(char *argv[]){
	/*---------------实现方法一----------------*/
		if(mkdir(argv[1],0775)!=0){  //return 0 if on success
			printf("create dir failed\\n");
		}
	
	/*---------------实现方法二----------------*/
	//	if(mkdirat(AT_FDCWD,argv[1],0775)!=0){  //argv[1]是绝对路径时,目录号被忽略
	//		printf("create dir failed\\n");
	//	}
	
}

6.rm

/*
实现rm函数
实现功能:根据绝对路径或者相对路径删除文件(必须是空文件)
用法:rm 1.txt 或 rmdir /home/zxf/1.txt
*/
void deleteFile(char *argv[]){
	//if(remove(argv[1])!=0){  //连文件夹都能删,感觉remove函数比较危险,换成unlink函数
	if(unlink(argv[1])!=0){
		printf("delete empty file failed\\n");
	}
}

7.cd

/*
实现cd函数
实现功能:进入相对路径或绝对路径,或回退到上一目录
用法:cd dir 或 cd /home/zxf/dir或 cd ..
*/
void cd(int argc, char *argv[]){
	char buf[100];
    if (chdir(argv[1])>=0)
    {
        getcwd(buf,sizeof(buf));  //get绝对路径 
        printf("Current absolute path is:%s\\n\\n",buf);
    }
}

8.rmdir

/*
实现rmdir函数
实现功能:根据绝对路径或者相对路径删除目录(必须是空目录)
用法:rmdir dir 或 rmdir /home/zxf/dir
*/
void deleteDir(char *argv[]){
	if(rmdir(argv[1])!=0){
		printf("delete empty dir failed\\n");
	}
}

9.wc

(1)实现功能

统计指定文件的行数、字符个数、单词个数

(2)实现步骤

1)统计文件行数
首先用fopen函数以只读方式打开文件,返回文件句柄,然后用fgets函数按行获取文件,每获取一行就让统计行数的变量加一。直到文件末尾,返回NULL,行数统计完毕。
关键代码:

while(fgets(s,200,f)!=NULL)
	{
		count_line++;
	}

2)统计单词个数
设计双层循环,最外层循环读取每一行,遇到一个空格符或换行符则单词数加一。也就是说以下的情况都作为一个单词:“abc”、“!!”、“{}”“__abc”。
关键代码:

int temp;
	while((temp=fgetc(f))!=EOF)
	{
		char temp2=temp;  //int 向 char转换
		if(temp2==' '||temp2=='\\n')
			count_word++;
	}

3)统计字符个数
统计字符个数用到了fgetc函数,以字符为单位读取文件。没读取一个字符,统计字符个数的变量加一。直到文件末尾。
关键代码:

while(fgetc(f)!=EOF)
	{
		count_char++;
	}

4)最后一定要关闭文件

(3)完整代码

/*
实现wc函数
实现功能:统计制定文件的行数、单词个数、字符个数
用法: wc shell.c
*/
void wc(char *argv[]){
	FILE *f,*f1,*f2,*f3;
	int count_line=0;
	int count_word=0;
	int count_char=0;
	char s[201];
	/*-------统计行数------*/
	if((f=fopen(argv[1],"r"))==NULL)
		printf("open file failed\\n");
	while(fgets(s,200,f)!=NULL)
	{
		count_line++;
	}
	printf("%d  ",count_line);
	/*-----统计单词数-------*/
	if((f=fopen(argv[1],"r"))==NULL)
		printf("open file failed\\n");
	int temp;
	while((temp=fgetc(f))!=EOF)
	{
		char temp2=temp;  //int 向 char转换
		if(temp2==' '||temp2=='\\n')
			count_word++;
	}
	printf("%d  ",count_word);
	/*------统计字符个数-----*/
	if((f=fopen(argv[1],"r"))==NULL)
		printf("open file failed\\n");
	while(fgetc(f)!=EOF)
	{
		count_char++;
	}
	printf("%d  ",count_char);
	
	/*------打印文件名------*/
	printf("%s\\n",argv[1]);
	fclose(f);

}

10.cp

/*
实现cp函数
实现功能:将一个文件拷贝到另一个文件
用法:cp 1.txt 2.txt
*/
void cp(char* argv[])  //自定义的一个外部命令:将argv[1]argv[2]的内容拷贝到argv[3]里
{  
   
  
   int fd1,fd2,n;
   char buf[512];
   
   fd1=open(argv[1],O_RDONLY);
   fd2=creat(argv[2],0644);
   
   while((n=read(fd1,buf, 512))>0)
          write(fd2,buf,n);
          
   close(fd1);
   close(fd2);

   
}

11. pwd

/*
实现pwd函数
实现功能:显示当前路径 
用法:pwd
*/
void pwd(){
	char buf[100];
    getcwd(buf,sizeof(buf));  //get当前路径 
    printf("Current absolute path is:%s\\n\\n",buf);
}

12.主函数部分(包括了输入输出重定向、管道)

int argc = 0;  //存放指令的参数个数 
char* argv[32] = {NULL};  //指令


/*
实现任意长度命令的分割 
举例:输入命令cat 1.txt ,则*argv[0] 存放cat; *argv[1]存放1.txt, *argv[2]存放NULL ,argc=3
返回0代表命令有效,返回-1代表命令无效 
*/
int div_command(char* buf)  
{
	if (buf == NULL)
		return -1;
	argc = 0;
	char* front = buf;  
	char* back = buf;   
	//不断分割命令成指针数组
	while (1)
	{
		while (*front == ' ')//避免用户在输入命令前输入了多个空格
			front++;
		if (*front == '\\0')
			break;
		back = front;
		while (*back != ' ' && *back != '\\0')//这两句,找到小字符串尾
			back++;
		if (*back == '\\0')
		{
			argv[argc++] = front;
			break;
		}
		*back = '\\0';    //一个字符串分割成多个字符串"mv hhhh aaaa\\0"->"mv\\0 hhhh\\0 aaaa\\0 null"
		argv[argc++] = front;//argv[i]里存小字符串的首地址
		front = back + 1;
	}
	argv[argc++] = NULL;    //argv[]的最后一位必须是NULL
	if (argc == 1)//输入的命令什么有用信息也没有
		return -1;
	return 0;
} 

/*
命令列表
*/
void list()
{
printf("----------------------------------------------------\\n");
      	printf("cd   : change path\\n");
	printf("pwd  : print your current path\\n");
	printf("ls   : list directory contents\\n");
	printf("echo : display a line of textn");
	printf("cat  : print file on the standard output\\n");
	printf("mkdir: create a new directory\\n"); 
	printf("rmdir: delete a directory\\n");
	printf("rm   : delete a file\\n");
	printf("wc   : print newline, word, and byte counts of a file \\n");
	printf("cp   : copy files\\n");
	printf(">    : output redirection\\n");
	printf("<    : input redirection\\n");
	printf("|    : pipeline\\n");
printf("----------------------------------------------------\\n");
}
int main()
{
  int i;
  char command[1024];//接收到的命令
  char *prompt="$ ";
  int  isBuiltin=0; //==1,是内置命令
  int   isCopy12=0; //==1,是自定义的copy12命令
  list();
  while(1)
  //循环接受命令
  {     //char absolutePath[30];
  	//getcwd(absolutePath,sizeof(absolutePath));
      	memset(command,0x00,1024);
      	
      	printf("yourname@ubuntu: $");
     	 //读取到\\n为止,删除缓冲区数据
      	scanf("%[^\\n]%*c", command);
      	以上是关于C语言实现shell (包括管道输入输出重定向)的主要内容,如果未能解决你的问题,请参考以下文章

重新点亮shell————管道和重定向[二]

Linux编程 22 shell编程(输出和输入重定向,管道,数学运算命令,退出脚本状态码)

Linux shell编程:管道和重定向

实现一个shell程序

十Shell篇——管道与重定向

带有管道的Shell实现和c中的I O重定向