实验三 C程序设计实验
实验目的:
巩固Make使用和C程序设计语言基本语法,加深对所学知识理解。
(1) 编写一 C 程序,采用标准 I/O 库显示文本文件的内容(cat 命令)。该程序的编译和链接用 make 工具来实现,要求先产生.o 文件,再产生可执行文件,并且在 makefile 文件中具备删除中间文件(.o)的功能。
(2)编写一 C 程序,显示当前目录下的所有文件名(ls 命令)。该程序的编译和链接用 make 工具来实现,要求先产生.o 文件,再产生可执行文件,并且在 makefile 文件中具备删除中间文件(.o)的功能。
(3) 编写一个 C 程序,改变当前进程的工作目录(cd 命令)。该程序的编译和链接用 make 工具来实现,要求先产生.o 文件,再产生可执行文件,并且在 makefile 文件中具备删除中间文件(.o)的功能
实验一:c语言实现cat命令
实验过程:
1、编写c文件实现cat命令
文件内容如下:
#include <stdio.h>
int main(int argc, char *argv[])
{
FILE *fp = fopen(argv[1], "r"); // 以只读方式打开文件,argv[1]是输入的要显示的文件路径名
int read_ret;
if(argc < 2) //说明没有输入要显示的文件名,只有本程序名argv[0]
{
printf("please input source file!\n");
}
if(fp == NULL) //如果打开成功的话就把该文件的信息结构体地址赋给文件指针fp,如果打开不成功的话就将文件指针fp置为无效(NULL)
{
printf("open source %s failed!\n", argv[1]);
return -1;
}
while(1) //成功打开
{
read_ret = fgetc(fp); //单个字符读写
if(feof(fp)) // feof判断文件结束,若fp已经指向文件末尾,则feof(fp)函数值为“真”,即返回非零值
{
printf("read file %s endl\n",argv[1]); //提示读写结束
break;
}
fputc(read_ret,stdout); //把单个字符输出到屏幕
}
}
2、编写makefile文件,编写编译过程
2.1、makefile的规则如下:
target ...
prerequisites ...
command...
target也就是一个目标文件,可以是object file,也可以是执行文件。
prerequisites就是,要生成那个target所需要的文件或是目标。
command也就是make需要执行的命令。(任意的shell命令)
这是一个文件的依赖关系,也就是说,target这一个或多个的目标文件依赖于 prerequisites中的文件,其生成规则定义在 command中。说白一点就是说,prerequisites中如果有一个以上的文件比target文件要新的话,command所定义的命令就会被执行。这就是makefile的规则。
2.2、clean
clean不是一个文件,它只不过是一个动作名字,有点像c语言中的lable一样,其冒号后什么也没有,那么,make就不会自动去找它的依赖性,也就不会自动执行其后所定义的命令。要执行其后的命令(不仅用于clean,其他lable同样适用),就要在make命令后明显得指出这个lable的名字。
.PHONY clean意思表示clean是一个“伪目标”。而在rm命令前面加了一个小减号的意思就是,也许某些文件出现问题,但不要管,继续做后面的事(假设手动删掉了一个文件,保证其他文件也能删除)。
2.3、makefile文件的命名
默认的情况下,make命令会在当前目录下按顺序找寻文件名为“GNUmakefile”、“makefile”、“Makefile”的文件,找到了解释这个文件。当然,也可以使用别的文件名来书写Makefile,比如:“Make.Linux”,“Make.Solaris”,“Make.AIX”等,如果要指定特定的Makefile,你可以使用make的“-f”和“--file”参数,如:make -f Make.Linux或make --file Make.AIX。
2.4、makefile文件的编写
使用vim编写makefile文件,编译cat.c文件,并编写相应的清除功能。
cat.o:cat.c
gcc -o cato cat.c
.PHONY : clean
clean :
-rm cato
3、编译、链接
编译:由于该makefile命名为Make.cat,故编译选择make -f Make.cat
链接:链接.out文件形成可执行代码。由于cat.c文件的函数需要实参,故链接时写入相应的实参,即文件名,由于本题要求cat输出代码的内容,故实参为cat.c
清除clean命令时候报错
后查明,因为我修改makefile文件名为Make.cat故执行其对应的clean命令需要输入文件名,故正确的输入为make -f Make.cat clean
4、实验结果
实验二:编写c语言实现ls命令
1、编写c语言实现ls命令
采用C语言编写程序,实现以下LS命令。
-a:显示所有档案及目录(ls内定将档案名或目录名称为“.”的视为影藏,不会列出);
-l:与“-C”选项功能相反,所有输出信息用单列格式输出,不输出为多列;
-d:仅显示目录名,而不显示目录下的内容列表。显示符号链接文件本身,而不显示其所指向的目录列表;
-i:显示文件索引节点号(inode)。一个索引节点代表一个文件;
-R:递归处理,将指定目录下的所有文件及子目录一并处理;
代码如下:
#include <stdio.h>
#include <sys/types.h>
#include <dirent.h>
#include <sys/stat.h>
#include <pwd.h>
#include <grp.h>
#include <unistd.h>
#include <string.h>
#define LS_NONE 0
#define LS_L 101
#define LS_R 102
#define LS_D 103
#define LS_I 104
#define LS_A 200
#define LS_AL (LS_A+LS_L)
#define LS_AI (LS_A+LS_I)
// 展示单个文件的详细信息
void show_file_info(char* filename, struct stat* info_p)
{
char* uid_to_name(), *ctime(), *gid_to_name(), *filemode();
void mode_to_letters();
char modestr[11];
mode_to_letters(info_p->st_mode, modestr);
printf("%s", modestr);
printf(" %4d", (int) info_p->st_nlink);
printf(" %-8s", uid_to_name(info_p->st_uid));
printf(" %-8s", gid_to_name(info_p->st_gid));
printf(" %8ld", (long) info_p->st_size);
printf(" %.12s", 4 + ctime(&info_p->st_mtime));
printf(" %s\n", filename);
}
void mode_to_letters(int mode, char str[])
{
strcpy(str, "----------");
if (S_ISDIR(mode))
{
str[0] = 'd';
}
if (S_ISCHR(mode))
{
str[0] = 'c';
}
if (S_ISBLK(mode))
{
str[0] = 'b';
}
if ((mode & S_IRUSR))
{
str[1] = 'r';
}
if ((mode & S_IWUSR))
{
str[2] = 'w';
}
if ((mode & S_IXUSR))
{
str[3] = 'x';
}
if ((mode & S_IRGRP))
{
str[4] = 'r';
}
if ((mode & S_IWGRP))
{
str[5] = 'w';
}
if ((mode & S_IXGRP))
{
str[6] = 'x';
}
if ((mode & S_IROTH))
{
str[7] = 'r';
}
if ((mode & S_IWOTH))
{
str[8] = 'w';
}
if ((mode & S_IXOTH))
{
str[9] = 'x';
}
}
char* uid_to_name(uid_t uid)
{
struct passwd* getpwuid(),* pw_ptr;
static char numstr[10];
if((pw_ptr = getpwuid(uid)) == NULL)
{
sprintf(numstr,"%d",uid);
return numstr;
}
else
{
return pw_ptr->pw_name;
}
}
char* gid_to_name(gid_t gid)
{
struct group* getgrgid(),* grp_ptr;
static char numstr[10];
if(( grp_ptr = getgrgid(gid)) == NULL)
{
sprintf(numstr,"%d",gid);
return numstr;
}
else
{
return grp_ptr->gr_name;
}
}
void do_ls(char dirname[],int mode)
{
DIR* dir_ptr;
struct dirent* direntp;
if ((dir_ptr = opendir(dirname)) == NULL)
{
fprintf(stderr, "ls2: cannot open %s \n", dirname);
}
else
{
if(mode==LS_D)
{
printf("%s\n", dirname);
}
else
{
char dirs[20][100];
int dir_count = 0;
while ((direntp = readdir(dir_ptr)) != NULL)
{
if(mode < 200 && direntp->d_name[0]=='.')
{
continue;
}
char complete_d_name[200]; // 文件的完整路径
strcpy (complete_d_name,dirname);
strcat (complete_d_name,"/");
strcat (complete_d_name,direntp->d_name);
struct stat info;
if (stat(complete_d_name, &info) == -1)
{
perror(complete_d_name);
}
else
{
if(mode == LS_L||mode == LS_AL)
{
show_file_info(direntp->d_name, &info);
}
else if(mode == LS_A||mode == LS_NONE||mode == LS_I||mode == LS_AI)
{
if(mode == LS_I||mode == LS_AI)
{
printf("%llu ", direntp->d_ino);
}
printf("%s\n", direntp->d_name);
}
else if(mode == LS_R)
{
if(S_ISDIR(info.st_mode))
{
printf("%s\n", direntp->d_name);
strcpy (dirs[dir_count],complete_d_name);
dir_count++;
}
else
{
printf("%s\n", direntp->d_name);
}
}
}
}
if(mode == LS_R)
{
int i=0;
printf("\n");
for(;i<dir_count;i++){
printf("%s:\n", dirs[i]);
do_ls(dirs[i],LS_R);
printf("\n");
}
}
}
closedir(dir_ptr);
}
}
// 解析一个单词参数,如-l,-i
int analyzeParam(char* input){
if(strlen(input)==2)
{
if(input[1]=='l') return LS_L;
if(input[1]=='a') return LS_A;
if(input[1]=='d') return LS_D;
if(input[1]=='R') return LS_R;
if(input[1]=='i') return LS_I;
}
else if(strlen(input)==3)
{
if(input[1]=='a'&& input[2]=='l') return LS_AL;
if(input[1]=='a'&& input[2]=='i') return LS_AI;
}
return -1;
}
int main(int ac,char* av[])
{
if(ac == 1)
{
do_ls(".",LS_NONE);
}
else
{
int mode = LS_NONE; // 默认为无参数ls
int have_file_param = 0; // 是否有输入文件参数
while(ac>1)
{
ac--;
av++;
int calMode = analyzeParam(*av);
if(calMode!=-1)
{
mode+=calMode;
}
else
{
have_file_param = 1;
do
{
printf("%s:\n", *av);
do_ls(*av,mode);
printf("\n");
ac--;
av++;
}while(ac>=1);
}
}
if (!have_file_param)
{
do_ls(".",mode);
}
}
}
2、编写makefile文件,编写编译过程
使用vim编写makefile文件,编译ls.c文件,并编写相应的清除功能。
ls.o:ls.c
gcc -o lso ls.c
.PHONY : clean
clean :
-rm ls0
3、编译、链接
编译:由于该makefile命名为Make.ls,故编译选择make -f Make.ls
链接:链接.out文件形成可执行代码。由于cat.c文件的函数需要实参,故链接时写入相应的实参,即文件名,由于本题要求cat输出代码的内容,故实参分别为-a,-i,-l,-R,-d
Ubuntu gcc编译报错:format ‘%llu’ expects argument of type ‘long long unsigned int’, but argument 2 has type ‘__time_t’ [-Wformat=]百度后得知gcc ls.c -Wformat=0 就没问题了。Wformat这个配置在Centos下默认是关闭的,所以一直没报错,如果编译的时候打开,也会提示一样的错误。
修改为:
ls.o:ls.c
gcc -o lso ls.c -Wformat=0
.PHONY : clean
clean :
-rm lso
4、实验结果
ls -a
ls -d ,ls -i
ls -l
ls -R
删除.o文件
实验三:编写c文件实现cd命令
1、编写c文件代码
#include<stdio.h>
#define PATH_SIZE 100
#define BUF_SIZE 64
int cds(const char *p)
{
char path[PATH_SIZE];
char *start;
char *end;
int res;
int n= 0;
memset(path,'\0',PATH_SIZE); // must init
start = strchr(p,' ');
end = strchr(p,'\n');
if(!start || !end)
{
printf("can't support this format \n");
return 1;
}
strncpy(path,p+3,end-start-1); // get the path in inputting command
res = chdir(path); //change dir
if(res != 0)
printf("%s is nod a path,please check again \n",path);
return res;
}
int pwds()
{
char buf[PATH_SIZE];
char *res;
res = getcwd(buf,PATH_SIZE);
if(res)
{
printf("%s\n",buf);
return 0;
}
else
return 1;
}
int main(int argc, char *argv[])
{
char buf[BUF_SIZE];
printf("|->");
fgets(buf,BUF_SIZE,stdin);
cds(buf);
printf("you have cd successfully,the directory now is:\n");
pwds();
}
2、编写makefile文件,实现cd.c文件的编译,以及相关文件的删除
cd.o:cd.c
gcc -o cdo cd.c
.PHONY : clean
clean :
-rm cdo
3、编译、链接
编译:由于该makefile命名为Make.cd,故编译选择make -f Make.cd
编译报错:
百度后得知,memset函数.需要包含include <string.h>故在源代码填上#include <string.h>
之后有报错
百度后知道了,char *getcwd(char *buf, size_t size);
作用:把当前目录的绝对地址保存到 buf 中,buf 的大小为 size。如果 size太小无法保存该地址,返回 NULL 并设置 errno 为 ERANGE。可以采取令 buf 为 NULL并使 size 为0来使 getcwd 调用 malloc 动态给 buf 分配,但是这种情况要特别注意使用后释放缓冲以防止内存泄漏。需要头文件#include <unistd.h>
故再填上#include <unistd.h>
链接:链接.out文件形成可执行代码。代码,./cdo
4、实验过程
清理.o文件(cd后是两个tab键,自动补全文件名)
实验总结
这次实验让我更加了解了make编译器的使用和编写,也对于.c文件的编译链接过程有更进一步理解和掌握。同时对于对于编写总出现bug有了进一步应对的措施,谨记不会就问度娘,其中针对implicit declaration of function xxx,大概率就是你运用的函数没有引用函数所在的头文件,可以百度一下该函数,就可以知道它所需要的头文件了,还有一种可能就是xxx是你编写的函数,但你在用它之前没有函数说明,需要在mian()函数前写一下所用函数的函数说明,就可以用了。
参考链接
https://www.cnblogs.com/aiguona/p/9162500.html
https://blog.csdn.net/qq_36946026/article/details/80273842
https://blog.csdn.net/skyejy/article/details/89922298
https://blog.csdn.net/itworld123/article/details/79348693?utm_source=blogxgwz2
https://blog.csdn.net/weixin_43387612/article/details/89259944
https://baike.baidu.com/item/getcwd/4746955?fr=aladdin
https://blog.csdn.net/abc13526222160/article/details/94853883
https://www.cnblogs.com/likui360/p/5275203.html
https://blog.csdn.net/xiao_xiaoli/article/details/12321667?locationNum=3