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<
  • 4
    点赞
  • 47
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
在 Linux 中,使用 C 语言模拟 shell 命令可以通过调用系统函数实现。以下是一个简单的例子,演示了如何使用 C 语言实现重定向管道通信。 首先,我们需要包含一些头文件: ```c #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <fcntl.h> ``` 接下来,我们可以定义一些常量,例如: ```c #define MAX_ARGS 10 #define MAX_BUFFER 1024 ``` 然后,我们可以定义一个函数来解析用户输入的命令,并将其分解为单个参数。以下是一个简单的实现: ```c void parse_command(char *command, char **args, int *redirect_input, int *redirect_output) { int arg_count = 0; char *token; char *rest = command; while ((token = strtok_r(rest, " ", &rest))) { if (*token == '<') { *redirect_input = open(token + 1, O_RDONLY); } else if (*token == '>') { *redirect_output = open(token + 1, O_WRONLY | O_CREAT | O_TRUNC, 0644); } else { args[arg_count++] = token; } } args[arg_count] = NULL; } ``` 此函数通过使用 `strtok_r()` 函数将命令分解为参数。如果命令包含输入重定向符 `<`,则将 `redirect_input` 指针设置为打开输入文件的文件描述符。如果命令包含输出重定向符 `>`,则将 `redirect_output` 指针设置为打开输出文件的文件描述符。在解析完成后,参数将存储在 `args` 数组中。 接下来,我们可以定义一个函数来处理管道通信。以下是一个简单的实现: ```c void pipe_commands(char **commands) { int fd[2]; pid_t pid1, pid2; char *args1[MAX_ARGS], *args2[MAX_ARGS]; if (pipe(fd) == -1) { perror("pipe"); exit(EXIT_FAILURE); } parse_command(commands[0], args1, NULL, &fd[1]); parse_command(commands[1], args2, &fd[0], NULL); pid1 = fork(); if (pid1 == -1) { perror("fork"); exit(EXIT_FAILURE); } else if (pid1 == 0) { close(fd[0]); dup2(fd[1], STDOUT_FILENO); close(fd[1]); execvp(args1[0], args1); } else { pid2 = fork(); if (pid2 == -1) { perror("fork"); exit(EXIT_FAILURE); } else if (pid2 == 0) { close(fd[1]); dup2(fd[0], STDIN_FILENO); close(fd[0]); execvp(args2[0], args2); } else { close(fd[0]); close(fd[1]); wait(NULL); wait(NULL); } } } ``` 此函数创建一个管道,然后使用 `parse_command()` 函数解析两个命令,并将其分别存储在 `args1` 和 `args2` 数组中。接下来,它调用 `fork()` 函数创建两个子进程,其中一个子进程执行第一个命令,另一个子进程执行第二个命令。使用 `dup2()` 函数将子进程的标准输出或标准输入连接到管道的适当端口。最后,主进程等待两个子进程完成。 最后,我们可以定义一个主函数来使用这些函数来执行用户输入的命令。以下是一个简单的实现: ```c int main() { char buffer[MAX_BUFFER]; char *commands[2]; int redirect_input = 0, redirect_output = 0; while (1) { printf("$ "); if (fgets(buffer, MAX_BUFFER, stdin) == NULL) break; commands[0] = strtok(buffer, "|"); if ((commands[1] = strtok(NULL, "\n")) != NULL) { pipe_commands(commands); } else { parse_command(commands[0], commands, &redirect_input, &redirect_output); pid_t pid = fork(); if (pid == -1) { perror("fork"); exit(EXIT_FAILURE); } else if (pid == 0) { if (redirect_input) { dup2(redirect_input, STDIN_FILENO); close(redirect_input); } if (redirect_output) { dup2(redirect_output, STDOUT_FILENO); close(redirect_output); } execvp(commands[0], commands); } else { wait(NULL); } } } return 0; } ``` 此函数使用 `fgets()` 函数从标准输入读取用户输入的命令。如果命令包含管道符 `|`,则使用 `strtok()` 函数将命令分解为两个命令,并使用 `pipe_commands()` 函数执行它们之间的管道通信。否则,就使用 `parse_command()` 函数解析命令,并使用 `fork()` 函数创建子进程来执行命令。在子进程中,使用 `dup2()` 函数将标准输入或标准输出重定向到适当的文件描述符。最后,主进程使用 `wait()` 函数等待子进程完成。 这就是使用 C 语言模拟 shell 命令的基本方法。请注意,此实现仅用于演示目的,并且可能需要进行更改以处理更多情况。
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值