目录
目录
1.1 Linux 开发环境搭建
必备软件:
Ubuntu 18.04
XShell-用于远程登录,使用SSH协议,TCP连接,端口号22
XFtp,本次实验中尚未用到
Visual studio code,安装扩展包:
Linux安装
sudo apt install openssh-server
VS code软件安装Remote Development插件,用于建立远程连接管理
同样使用SSH建立连接
使用ctrl+L能够使得SSH软件清屏
为了便于后期操作,windows端与ubuntu端使用相同的公钥
ssh-keygen -t rsa
此处也可以记录一下原本的过程:
(1)远程主机收到用户的登录请求,把自己的公钥发给用户。
(2)用户使用这个公钥,将登录密码加密后,发送回来。
(3)远程主机用自己的私钥,解密登录密码,如果密码正确,就同意用户登录。
中文软件包
大体本节实验就结束了
1.2 GCC
1.2.1 GCC简介
GCC(GNU Compiler Collection,GNU编译器套件)是由GNU开发的编程语言编译器。GNU还有其他语言,例如Java、Go的编译器套件,也包括了这些语言的库
GCC不仅支持C,还可以区分不同的C语言标准,支持用命令行选项来控制编译器在编译源代码时应该遵循哪个C标准。
查看标准可以使用如下命令
gcc/g++ -v/–version
1.2.2 vim命令
1.打开命令: vim a.log (注意后缀名要加上)
2.退出命令:按ESC键 跳到命令模式,然后输入 :q (不保存退出) 或者 :wq (保存退出)。
3.注意 以 : 和 / 开头的命令都有历史纪录,可以首先键入 : 或 / 然后按上下箭头来选择某个历史命令。
:w 保存文件但不退出vi
:w file 将修改另外保存到file中,不退出vi
:w! 强制保存,不推出vi
:wq 保存文件并退出vi
:wq! 强制保存文件,并退出vi
:q 不保存文件,退出vi
:q! 不保存文件,强制退出vi
:e! 放弃所有修改,从上次保存文件开始再编辑命令历史
编译文件的命令格式如下:
gcc 文件名.扩展名 -o 输出文件名
运行文件
./输出文件名
1.2.3 gcc工作原理
预处理:指令-E
,举例如下:
gcc test.c -E -o test.i
编译:指令-S
gcc test.i -S -o test.s
汇编:指令-c
gcc test.s -s -o test.o
.o
文件已是可执行文件
rm--删除文件
1.2.4 gcc和g++的区别
第四个内容有关gcc和g++的区别
首先声明gcc和g++都是GNU(组织)的一个编译器
误区1:gcc只能用于编译c代码,g++只能编译c++代码。但实际上两者都可以
后缀为.c的gcc把它当做c程序,而g++当做是c++程序
后缀为.cpp的,两者都会认为是c++程序,c++的语法规则更加严谨一些
编译阶段,g++会调用gcc,对于c++代码,两者等价,但因为gcc命令不能自动和c++程序使用的库链接,所以通常用g++完成链接。
为了统一,索性直接用g++完成编译、链接,但实际上,gcc也可以完成.cpp程序的编译
误区2:gcc不会定义__cplusplus宏,而g++会
实际上,该宏只是标志着编译器会把代码按照c还是c++语法来解释
如果后缀为.c,并且采用gcc编译器,那么宏就是未定义的;使用g++就是已定义
误区3:编译只能用gcc,链接只能用g++
严格来说,这句话不错,但是混淆了概念,应该说编译二者都可以用,而链接则可以用g++或者gcc -lstdc++
gcc命令不能自动和C++程序使用的库链接,所以通常用g++来完成链接,但在编译阶段,二者等价
1.2.5 GCC常用选项
1.3 静态库的制作
1.3.1什么是库
1.3.2 静态库命名规则:
1.3.3 制作静态库
意思是:把calc目录下的libcalc.a文件复制到当前目录的lib/文件夹中
编译main.c时出现问题,此时是因为include目录下的head.h没有引用到
出现未定义库问题时,指定库
使用静态库的完整步骤
gcc -c add.c sub.c mult.c div.c -I ../include/ //首先通过gcc把.c文件编译成.o文件
ar rcs libsuanshu.a add.o div.o mult.o sub.o //把.o文件打包成libsuanshu.a文件
mv libsuanshu.a ../lib/ //把libsuanshu.a移动到lib/文件夹中
gcc main.c -o app -I ./include/ -l suanshu -L ./lib //main.c引用到suanshu静态库生成app文件
1.4动态库的制作和使用
编译过程中出现问题:
解决方法:
如上
出现问题2
问题:动态库文件寻找不到
解决方法
方法一:在终端配置(暂时性作用),当终端结束后,作用消失
寻找到动态库的地址
把地址添加到LD_LIBRARY_PATH
方法二:永久配置
方法2.1 修改.bashrc文件
vim .bashrc 打开文件
shift+g 然后按o向下插入一行
然后
source .bashrc
方法2.2
1.5静态库与动态库的对比
1.6 Makefile
1.6.1Makefile文件命名和规则
1.6.2 变量和模式匹配![](https://i-blog.csdnimg.cn/blog_migrate/cb5d7d1671139b19aa46a73774ad9ffd.png)
:
make clean 执行清除文件操作
1.7GDB调试
打断点
查看断点信息
1.8文件IO
1.8.1标准C库IO函数
1.8.2标准C库IO和Linux系统IO的关系![](https://i-blog.csdnimg.cn/blog_migrate/5c5aa12e9cf8f0e2fd405fea0abb5e00.png)
1.8.3虚拟地址空间
1.8.4文件描述符
一个应用程序通过要求内核打开相应的文件,来宣告它想要访问一个IO设备。内核返回一个小的非负整数,叫做描述符.它在后续对此文件的所有操作中标识这个文件。内核记录有关这个打开文件的所有信息。应用程序只需要记住这个描述符。
1.8.5 Linux系统IO函数![](https://i-blog.csdnimg.cn/blog_migrate/8aecaf6fca72f74b26a60da3a3574809.png)
open函数打开文件
man 2 open
看open的函数
/*
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
// 打开一个已经存在的文件
int open(const char *pathname, int flags);
参数:
- pathname:要打开的文件路径
- flags:对文件的操作权限设置还有其他的设置
O_RDONLY, O_WRONLY, O_RDWR 这三个设置是互斥的
返回值:返回一个新的文件描述符,如果调用失败,返回-1
errno:属于Linux系统函数库,库里面的一个全局变量,记录的是最近的错误号。
#include <stdio.h>
void perror(const char *s);作用:打印errno对应的错误描述
s参数:用户描述,比如hello,最终输出的内容是 hello:xxx(实际的错误描述)
// 创建一个新的文件
int open(const char *pathname, int flags, mode_t mode);
*/
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
int main() {
// 打开一个文件
int fd = open("a.txt", O_RDONLY);
if(fd == -1) {
perror("open");
}
// 读写操作
// 关闭
close(fd);
return 0;
}
open创建新文件
/*
int open(const char *pathname, int flags, mode_t mode);
参数:
-pathname:要创建的文件的路径
-flags:对文件的操作权限和其他的设置
--必选项:O_RDONLY,O_WRONLY,O_RDWR,这三个是互斥的
--可选项: O_CREAT文件不存在,创建新文件
-mode:八进制的数,表示用户对创建出的新文件的操作权限 比如:0775 rwx
最终的权限是:mode & ~umask
0777 ->111111111
&0775 ->111111101 =111111101=0775
--------------------------
umask的作用就是抹去某些权限
flags参数是一个int类型的数据,占4个字节,32位
flags 32个位,每一位就是一个标志位
mode_t umask(mode_t mask);
*/
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
int main()
{
//创建一个新文件
int fd = open("create.txt",O_RDWR|O_CREAT,0777);
if(fd==-1)
{
perror("open");
}
//关闭
close(fd);
return 0;
}
read and write函数
/*
#include <unistd.h>
ssize_t read(int fd, void *buf, size_t count);
参数:
- fd:文件描述符,open得到的,通过这个文件描述符操作某个文件
- buf:需要读取数据存放的地方,数组的地址(传出参数)
-count:指定的数组的大小
返回值:
-成功:
>0:返回实际的读取到的字节数
=0:文件已经读完
-1:文件读取失败,并且设置为errno
#include <unistd.h>
ssize_t write(int fd, const void *buf, size_t count);
参数:
-fd:文件描述符,open得到的,通过这个文件描述符
-buf:要往磁盘写入的数据,数据
-count:要写的数据的实际的大小
返回值:
成功:实际写入的字节数
失败:返回-1,并写入errno
*/
#include <unistd.h>
#include<stdio.h>
#include<sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main()
{
//通过open打开englist.txt
int srcfd = open("englist.txt",O_RDONLY);
if(srcfd ==-1)
{
perror("open");
return -1;
}
//创建一个新的文件
int destfd = open("cpy.txt",O_WRONLY|O_CREAT,0664);
if(destfd==-1)
{
perror("open");
return -1;
}
//频繁的读写操作
char buf[1024] ={0};
int len =0;
len = read(srcfd,buf,sizeof(buf));
while(len>0)
{
write(destfd,buf,len);
}
//关闭文件
close(destfd);
close(srcfd);
return 0;
}
lseek函数
/*
标准C库函数
#include <stdio.h>
int fseek(FILE *stream, long offset, int whence);
#include <sys/types.h>
#include <unistd.h>
off_t lseek(int fd, off_t offset, int whence);
参数:
-fd:文件描述符,通过open得到的,通过这个fd操作某个文件
-offset:偏移量
-whence:
SEEK_SET
设置文件指针的偏移量
SEEK_CUR
设置偏移量:当前位置+第二个参数offset的值
SEEK_END
设置偏移量:文件大小+第二个参数offset的值
返回值:返回文件指针的位置
作用:
1.移动文件指针到文件头
lseek(fd,0,SEEK_SET);
2.获取当前文件指针的位置
lseek(fd,0,SEEK_CUR);
3.获取文件长度
lseek(fd,0,SEEK_END);
4.拓展文件的长度,当前文件10b,100b,增长了100个字节
lseek(fd,100,SEEK_END)
*/
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include <stdio.h>
#include<unistd.h>
int main()
{
int fd =open("hello.txt",O_RDWR);
if (fd==-1)
{
perror("open");
return -1;
}
//写入一个空数据
write(fd," ",1);
//拓展文件长度
int ret =lseek(fd,100,SEEK_END);
if(ret==-1)
{
perror("lseek");
return -1;
}
close(fd);
return 0;
}
stat函数
在Linux系统中,stat()
是一个C语言函数,用于获取文件的详细信息。它可以用于检索文件的权限、所有者、大小、创建时间和修改时间等信息。
/*
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
int stat(const char *pathname, struct stat *statbuf);
作用:获取一个文件相关的一些信息
参数:
-pathname:操作的文件的路径
-statbuf:结构体变量,传出参数,用于保存获取到的信息
返回值:
成功:返回0
失败:返回-1,设置errno
int fstat(int fd, struct stat *statbuf);
int lstat(const char *pathname, struct stat *statbuf);
*/
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include<stdio.h>
int main()
{
struct stat statbuf;
int ret= stat("a.txt",&statbuf);
if(ret==-1)
{
perror("stat");
return -1;
}
printf("size:%ld\n",statbuf.st_size);
return 0;
}
模拟实现ls-l命令
//模拟ls -l命令
//-rw-rw-r-- 1 su su 11 Feb 17 22:47 a.txt
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <pwd.h>
#include <grp.h>
#include <time.h>
#include <string.h>
int main(int argc,char *argv[])
{
if(argc<2)
{
printf("%s filename\n",argv[0]);
return -1;
}
//通过stat函数获取用户传入文件信息
struct stat st;
int ret=stat(argv[1],&st);
if(ret==-1)
{
perror("stat");
return -1;
}
//获取文件类型和文件权限
char perms[11]={0};
switch(st.st_mode& __S_IFMT)
{
case __S_IFLNK:
perms[0] ='l';
break;
case __S_IFDIR: //目录
perms[0] ='d';
break;
case __S_IFREG: //普通文件
perms[0] ='-';
break;
case __S_IFBLK: //块设备
perms[0] ='b';
break;
case __S_IFCHR: //字符设备
perms[0] ='c';
break;
case __S_IFSOCK: //套接字
perms[0] ='p';
break;
default:
perms[0]='?';
break;
}
//判断文件的访问权限
//文件所有者
perms[1] = (st.st_mode & S_IRUSR) ? 'r' : '-';
perms[2] = (st.st_mode & S_IWUSR) ? 'w' : '-';
perms[3] = (st.st_mode & S_IXUSR) ? 'x' : '-';
// 文件所在组
perms[4] = (st.st_mode & S_IRGRP) ? 'r' : '-';
perms[5] = (st.st_mode & S_IWGRP) ? 'w' : '-';
perms[6] = (st.st_mode & S_IXGRP) ? 'x' : '-';
// 其他人
perms[7] = (st.st_mode & S_IROTH) ? 'r' : '-';
perms[8] = (st.st_mode & S_IWOTH) ? 'w' : '-';
perms[9] = (st.st_mode & S_IXOTH) ? 'x' : '-';
// 硬连接数
int linkNum = st.st_nlink;
// 文件所有者
char * fileUser = getpwuid(st.st_uid)->pw_name;
// 文件所在组
char * fileGrp = getgrgid(st.st_gid)->gr_name;
// 文件大小
long int fileSize = st.st_size;
// 获取修改的时间
char * time = ctime(&st.st_mtime);
char mtime[512] = {0};
strncpy(mtime, time, strlen(time) - 1);
char buf[1024];
sprintf(buf, "%s %d %s %s %ld %s %s", perms, linkNum, fileUser, fileGrp, fileSize, mtime, argv[1]);
printf("%s\n", buf);
return 0;
}
文件属性操作函数
在Linux中,access()
函数用于检查当前进程是否可以访问一个给定的文件路径。
access()
函数需要两个参数:文件路径名和一个表示请求访问的方式的整数值。其中,0表示检查文件是否存在,也就是判断当前进程是否能够打开这个文件。
/*
#include <unistd.h>
int access(const char *pathname, int mode);
作用:判断某个文件是否有某个权限,或者判断文件是否存在
参数:
-pathname:判断文件路径
-mode:
R_OK:判断是否有读权限
W_OK:判断是否有写权限
*/
#include <unistd.h>
#include<stdio.h>
int main()
{
int ret=access("a.txt",F_OK);
if(ret==-1)
{
perror("access");
}
printf("文件存在\n");
return 0;
}
chmod()函数
在Linux中,chmod()
函数用于修改文件或目录的权限模式。它的原型定义在sys/stat.h
头文件中,函数的参数包括要修改权限的文件或目录的路径名,以及用来设置新的权限模式的一个八进制数值。
/*
#include <sys/stat.h>
int chmod(const char *pathname, mode_t mode);
修改文件的权限
参数:
-pathname:修改文件路径
-mode:需要修改权限值,八进制的数
返回值:成功返回0,失败返回-1
int fchmod(int fd, mode_t mode);
*/
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
int main()
{
int ret=chmod("a.txt",0775);
if(ret==-1)
{
perror("chmod");
return -1;
}
return 0;
}
chown()函数
在Linux中,chown()
函数用于修改文件或目录的所有者和所属组。它的原型定义在unistd.h
头文件中,函数的参数包括要修改所有者和所属组的文件或目录的路径名,以及新的所有者和所属组的用户ID和组ID。
/*
#include <unistd.h>
int chown(const char *pathname, uid_t owner, gid_t group);
查看ownerid指令 :vim /etc/passwd
查看groupid指令 :vim /etc/group
*/
truncate()函数
在Linux中,truncate()
函数用于截断文件的大小。它的原型定义在unistd.h
头文件中,函数的参数包括要截断大小的文件路径名,以及截断后的大小。
/*
#include <unistd.h>
#include <sys/types.h>
int truncate(const char *path, off_t length);
作用:缩减或者拓展文件的尺寸至指定大小
参数:
-path:需要修改文件的路径
-length:需要最终文件变成的大小
返回值:成功返回0,失败返回-1
*/
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
int main()
{
int ret=truncate("a.txt",20);
if(ret==-1)
{
perror("truncat");
return -1;
}
return 0;
}
mkdir()函数
创建目录,/path/to/newdir
表示要创建的目录路径,0755表示新目录的权限模式。其中,第一个数字0表示这是一个八进制数值,755表示所有者、组和其他用户都具有读、写和执行权限。
/*
#include <sys/stat.h>
#include <sys/types.h>
int mkdir(const char *pathname, mode_t mode);
作用:创建一个目录
参数:
pathname:创建目录的路径
mode:权限,八进制的数
返回值:成功返回0,失败返回1
*/
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
int main()
{
int ret =mkdir("aaa",777);
if(ret==-1)
{
perror("mkdir");
return -1;
}
return 0;
}
rename函数
/*
#include <stdio.h>
int rename(const char *oldpath, const char *newpath);
*/
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
int main()
{
int ret =rename("aaa","bbb");
if(ret==-1)
{
perror("rename");
return -1;
}
return 0;
}
chdir函数
/*
#include <unistd.h>
int chdir(const char *path);
作用:修改进程的工作目录
比如在/home/nowcoder 启动了一个可执行程序a.out, 进程的工作目录 /home/nowcoder
参数:
path : 需要修改的工作目录
#include <unistd.h>
char *getcwd(char *buf, size_t size);
作用:获取当前工作目录
参数:
- buf : 存储的路径,指向的是一个数组(传出参数)
- size: 数组的大小
返回值:
返回的指向的一块内存,这个数据就是第一个参数
*/
#include <unistd.h>
#include <stdio.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
int main() {
// 获取当前的工作目录
char buf[128];
getcwd(buf, sizeof(buf));
printf("当前的工作目录是:%s\n", buf);
// 修改工作目录
int ret = chdir("/home/nowcoder/Linux/lesson13");
if(ret == -1) {
perror("chdir");
return -1;
}
// 创建一个新的文件
int fd = open("chdir.txt", O_CREAT | O_RDWR, 0664);
if(fd == -1) {
perror("open");
return -1;
}
close(fd);
// 获取当前的工作目录
char buf1[128];
getcwd(buf1, sizeof(buf1));
printf("当前的工作目录是:%s\n", buf1);
return 0;
}
opendir()函数
在Linux中,opendir()
函数用于打开一个目录,并返回指向目录流的指针。它的原型定义在dirent.h
头文件中,函数的参数包括要打开的目录路径名。
readdir()
在Linux中,readdir()
函数用于读取一个目录中的内容。它的原型定义在dirent.h
头文件中,函数的参数包括要读取的目录流指针。
closedir()函数
在Linux中,closedir()
函数用于关闭由opendir()
函数打开的目录流。它的原型定义在dirent.h
头文件中,函数的参数包括要关闭的目录流的指针。
统计目录下普通文件的个数
/*
//打开一个目录
#include <sys/types.h>
#include <dirent.h>
DIR *opendir(const char *name);
参数:
-name:需要打开的目录的名称
返回值:
DIR*类型,理解为目录流
//读取目录中的数据
#include <dirent.h>
struct dirent *readdir(DIR *dirp);
-参数:dirp是opendir返回的结果
-返回值:
struct dirent,代表读取到的文件信息
读取到了末尾或者失败了,返回NULL
//关闭目录
#include <sys/types.h>
#include <dirent.h>
int closedir(DIR *dirp);
*/
#include <sys/types.h>
#include <dirent.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
int getFileNum(const char*path);
//读取到某个目录下所有的普通文件的个数
int main(int argc,char*argv[])
//argc是一个整数类型的参数,表示命令行参数的数量,包括程序本身
//argv是一个指向字符指针数组的指针,每个指针指向一个命令行参数的字符串。
{
if(argc<2)
{
printf("%s path\n",argv[0]);
return -1;
}
int num =getFileNum(argv[1]);
printf("普通文件的个数为:%d\n",num);
return 0;
}
//用于获取目录下所有普通文件的个数
int getFileNum(const char*path)
{
//1.打开目录
DIR*dir =opendir(path);
if(dir==NULL)
{
perror("opendir");
exit(0);
}
struct dirent *ptr ;
//readdir(dir)==NULL时说明已读到文件结尾
//记录普通文件的个数
int total =0;
while(ptr =readdir(dir)!=NULL)
{
//获取名称
char * dname=ptr->d_name;
//忽略掉.和..
if(strcmp(dname,".")==0||strcmp(dname,"..")==0)
{
continue;
}
//判断是否是普通文件还是目录
if(ptr->d_type==DT_DIR)
{
//目录,需要继续读取这个目录
char newpath[256];
sprintf(newpath,"%s/%s",path,dname);
total+=getFileNum(newpath);
}
if(ptr->d_type ==DT_REG)
{
//普通文件
total++;
}
}
//关闭目录
closedir(dir);
return total;
}