01 Linux系统编程入门

第1章 Linux系统编程入门

1.1 Linux开发环境搭建

  1. 环境搭建的详细过程见牛客视频
  2. (技巧)以普通用户登录,在终端写一些有权限的命令时,需要在命令前加sudo,即以超级管理员身份运行,如:sudo ./vmware.install.pl
  3. (技巧)在输入命令时,可以使用tab键进行自动补全
  4. 如果虚拟机和主机之间不能复制,则通过在终端中分别输入以下指令后并重启计算机即可
sudo apt-get autoremove open-vm-tools
sudo apt-get install open-vm-tools-desktop 
  1. (技巧)创建文件夹命令mkdir,改变当前路径用cd
  2. 如果没有vim可以通过sudo apt install vim进行安装,其中aptAdvanced Package Tool的简写,可以简化软件管理
  3. (技巧)vim的退出并保存操作方法:先按esc,再输入:wq(输入:q是直接退出)

1.2 GCC(1)

  1. GCC的定义

    GCC原为GNU C语言编译器(GNU C Compiler),后变为GUN编译器套件(GNU Compiler Collection),可支持多个编程及其标准

  2. 安装命令(gcc和g++是用于编译c和c++语言的工具)

    sudo apt install gcc g++
    
  3. 查看gcc和g++版本方法

    gcc -v 或者 gcc --version
    g++ -v 或者 g++ --version
    
  4. 编译流程(高级语言通过编译得到汇编语言,再通过汇编得到机器语言,最后链接变成可执行文件)

image-20220729202620352

  1. gcc工作流程

image-20220729202811527

gcc test.c -E -o test.i  // 该操作为预处理,即将源文件的头文件展开,通过-E进行预处理
gcc test.i -S -o test.s  // 该操作为编译,即将预处理后的源代码变成汇编,通过-S进行编译
gcc test.s -C -o test.o  // 该操作为汇编,即将汇编代码变为机器码(0、1)
gcc test.c  // 直接获得最后的out文件,其实经历了上面三个步骤。默认生成的文件名为a.out
./a.out  // 运行该out程序
// (技巧)其中-o是命名操作
  1. (技巧)ctrl+l是清屏操作

1.3 GCC(2)

  1. 误区1:gcc只能编译c,g++只能编译c++

    • 后缀为.c的,gcc视为c程序,而g++视为c++程序
    • 后缀为.cpp的,gcc和g++都视为c++程序
    • 在编译阶段,gcc命令不能自动和c++程序使用的库链接,通常用g++来完成链接,故为了统一就用g++了
  2. 误区2:gcc不会定义_cpluscplus宏,而g++会

    • 如果后缀是.c且用gcc编译则宏是未定义的,其余情况都是定义的
  3. 误区3:编译只能用gcc,链接只能用g++

    • 实际上编译可以用gcc/g++,链接可以用g++或者gcc -lstdc++
    • 编译时gcc会自动调用gcc,gcc不能自动和c++程序库链接,通常使用g++

总之,尽可能使用过gcc处理c,g++处理c++

  1. GCC常用参数

image-20220729222400726

image-20220730122538540

1.4 静态库的制作

  1. 库的定义:

    • 库是计算机文件,类似于代码仓

    • 编写库程序和普通程序差别不大,只是不能单独运行

  2. 库的分类

    • 静态库(链接时复制到了程序中)
    • 动态库(链接时没有复制到程序中,而是程序运行时由系统动态加载)
  3. 库的优点

    • 代码保密
    • 方便部署和分发
  4. 静态库的制作

    • 命名规则:Linux:libxxx.a(前缀是lib,xxx是自己起的名字,库的名字.a是后缀);windows:libxxx.lib
    • 制作流程
      • gcc获得.o文件(只进行汇编,不进行链接):gcc -c xxx.c,输入后获得xxx.o
      • 用ar工具(archive)将.o文件打包:ar rcs libxxx.a xxx.o,r是将文件插入备存设备,c是建立备存文件,s是索引
  5. (技巧)安装tree:sudo apt install tree,如果遇到以下错误:

image-20220731201809410

则使用:sudo rm /var/lib/dpkg/lock进行强制解锁

  1. (技巧)ls是获取当前文件夹下的所有文件,ll是不仅包括所有文件,还包括具体的文件信息

1.5 静态库的使用

  1. (技巧)cd ..是返回上一级目录(一个点是当前目录)

  2. (技巧)cp -r calc/ library/ ../lesson05cp是拷贝,-r是以递归方式(针对文件夹),后面是两个包,../lesson05是目标文件夹

  3. (技巧)rm xxx删除xxx文件

  4. 步骤:

    1. .a文件拷贝到目标的lib文件,并将.a所需要的头文件同样需要拷贝到include文件中
    2. gcc main.c -o app -I ./include/ -L ./lib/ -l calc,其中-I是去找头文件,-L是去找静态库文件夹,-l是调用库的名称calc,而非文件的名称libcalc,最后会生成app,通过./app执行

1.6 动态库的制作和使用

  1. 命名规则:Linux:libxxx.so(前缀是lib,xxx是自己起的名字,库的名字.so是后缀);windows:libxxx.dll

  2. 制作:

    • gcc得到.o文件,得到和位置无关的代码:gcc -c -fpic/-fPIC xxx.c xxx.c
    • gcc得到动态库:gcc -shared xxx.o xxx.o -o libcalc.so
  3. (技巧)rm *.o可以统一删除后缀为.o的文件

  4. 使用:与静态库一样,需要导入头文件和库文件,但是会报错,下一节会说明原因。

1.7 动态库加载失败的原因

  1. 静态库:GCC进行链接时,会把静态库中代码打包到可执行程序中

  2. 动态库:GCC进行链接时,动态库的代码不会被打包到可执行程序中

    • 程序启动后,动态库会被动态加载到内存中,通过ldd命令检查动态库依赖关系
    • 定位方式:系统加载可执行代码时,不仅需要知道所依赖库的名字,还需要知道绝对路径,需要系统的动态载入库来获取该绝对路径

image-20220801194944740

1.8 解决动态库加载失败问题

由于DT_RPATH断不可改变,所以从第二种开始

  1. 法1:export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/chaopro/Linux/lesson06/library/lib,其中export是导入,LD_LIBRARY_PATH是路径名,$LD_LIBRARY_PATH是添加自己,之后用:隔开,后面路径是.so动态库所在路径。但是该方法只是临时创建的路径,当关闭会话,打开一个新会话后直接执行文件依然会报之前的错误。

(技巧):可以在会话上右击,复制一个新的会话,进而去找so文件路径,pwd命令是返回当前路径名称

image-20220801201058721

  1. 法2:用户级别设置,首先进入根目录的.bashrc文件:vim ~/.bashrc/;在最后一行插入export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/chaopro/Linux/lesson06/library/lib,保存并退出;之后使用source ~/.bashrc 或者. ~/.bashrc刷新当前的shell环境

(技巧)只有cd表示退回到根目录,~/是根目录;vim中shift+G将光标移至最后一行,o向下是插入1行,dd是删除光标行

(技巧)若不小心误删了.bashrc中的内容,则可以通过cp /etc/skel/.bashrc ~/恢复

1.9 静态库和动态库的对比

  1. 静态库的优点:
    • 静态库被打包到应用程序中加载速度快
    • 发布程序无需提供静态库,移植方便
  2. 静态库的缺点:
    • 消耗系统资源,浪费内存
    • 更新、部署、发布麻烦
  3. 动态库的优点:
    • 可以实现进程间资源共享(共享库)
    • 更新、部署、发布简单
    • 可以控制何时加载动态库
  4. 动态库的缺点:
    • 加载速度比静态库慢
    • 发布程序时需要提供依赖的动态库

1.10 Makefile(1)

  1. Makefile的定义(可以理解为存储编译的一个脚本,当再次编译时直接运行脚本即可)
    • Makefile 文件定义了一系列的规则来指定哪些文件需要先编译,哪些文件需要后编译,哪些文件需要重新编译,甚至于进行更复杂的功能操作,因为 Makefile 文件就像一个 Shell 脚本一样,也可以执行操作系统的命令
    • Makefile 带来的好处就是“自动化编译” ,一旦写好,只需要一个make命令,整个工程完全自动编译,极大的提高了软件开发的效率。
    • make 是一个命令工具,是一个解释 Makefile 文件中指令的命令工具,一般来说,大多数的 IDE 都有这个命令,比如 Delphi 的 make, Visual C++ 的 nmake, Linux 下 GNU 的 make。
  2. (技巧)解压缩包
tar -xvf file.tar //解压 tar包

tar -xzvf file.tar.gz //解压tar.gz

tar -xjvf file.tar.bz2   //解压 tar.bz2

tar -xZvf file.tar.Z   //解压tar.Z

unrar e file.rar //解压rar

unzip file.zip //解压zip
  1. Makefile 文件命名和规则

    • 文件命名:makefile或者Makefile

    • 规则:

      image-20220804222434691

  2. 安装make指令:sudo apt install make

  3. 步骤:先写Makefile文件内容,再用make执行(如下代码)

app:sub.c add.c mult.c div.c main.c
        gcc sub.c add.c mult.c div.c main.c -o app

1.11 Makefile(2)

  1. (技巧)vim中输入i进入编辑模式,按下esc退出编辑

  2. 工作原理

    1. 命令在执行前先检查规则中的依赖是否存在

      • 如果存在则执行;
      • 如果不存在,则向下检查其他规则,检查有没有规则是用来形成依赖的,如果找到了则执行(如下代码)。
      • Makefile中的其它规则一般都是为第一条规则服务的,make默认执行第一条规则,如果下面的规则和第一条规则无关则不会执行
      app:sub.o add.o mult.o div.o main.o
              gcc sub.o add.o mult.o div.o main.o -o app
      
      sub.o:sub.c
              gcc -c sub.c -o sub.o
      
      add.o:add.c
              gcc -c add.c -o add.o
      
      mult.o:mult.c
              gcc -c mult.c -o mult.o
      
      div.o:div.c
              gcc -c  div.c -o div.o
      
      main.o:main.c
              gcc -c  main.c -o main.o
      
    2. 检测更新,在执行规则中的命令时,会比较目标和依赖文件的时间

      • 如果依赖的时间比目标的时间晚,需要重新生成目标
      • 如果依赖的时间比目标的时间早,目标不需要更新,对应规则中的命令不需要被执行
      • 所以把规则分开写效率较高

1.12 Makefile(3)

  1. (技巧)vim中#是注释

  2. 变量

    • 自定义变量:变量名=变量值,例如var=hello

    • 预定义变量(已经定义好了)

      AR归档维护程序的名称,默认值为 ar
      CCC 编译器的名称,默认值为 gcc
      CXXC++ 编译器的名称,默认值为 g++
      $@目标的完整名称
      $<第一个依赖文件的名称
      $^所有的依赖文件
      • 获取变量的值 :$例如$(CC)等价于cc。注意:定义变量只能在规则的命令中使用
      • 继续优化代码
    #定义变量
    src=sub.o add.o mult.o div.o main.o
    target=app
    
    $(target):$(src)
            $(CC) $(src) -o $(target)
    
    sub.o:sub.c
            gcc -c sub.c -o sub.o
    
    add.o:add.c
            gcc -c add.c -o add.o
    
    mult.o:mult.c
            gcc -c mult.c -o mult.o
    
    div.o:div.c
            gcc -c  div.c -o div.o
    
    main.o:main.c
            gcc -c  main.c -o main.o
    
  3. 模式匹配

    • %是通配符,匹配一个字符串,例如:
    %.o:%.c
    	$(CC) -c $< -o $@
    
    • 再次优化代码
    #定义变量
    src=sub.o add.o mult.o div.o main.o
    target=app
    
    $(target):$(src)
            $(CC) $(src) -o $(target)
    
    %.o:%.c
            $(CC) -c $< -o $@
    
  4. 常见函数

    1. 函数1:$(wildcard PATTERN...)

      • 功能:获取指定目录下指定类型的文件列表
      • 参数:PATTERN 指某个或多个目录下的对应的某种类型的文件,如果有多个目录,一般使用空格间隔
      • 返回:得到的若干个文件的文件列表,文件名之间使用空格间隔
      • 例如:
      $(wildcard *.c ./sub/*.c)
      返回值格式: a.c b.c c.c d.c e.c f.c
      
    2. 函数2:$(patsubst <pattern>,<replacement>,<text>)

      • 功能:查找 中的单词( 单词以“空格”、“Tab”或“回车”“换行”分隔)是否符合,如果匹配的话,则以替换
      • 可以包括通配符%,表示任意长度的字串。如果中也包含%,那么, 中的这个%将是中的那个%所代表的字串。 (可以用\来转义,以\%来表示真实含义的%字符)
      • 返回:函数返回被替换过后的字符串
      • 例如:
      $(patsubst %.c, %.o, x.c bar.c)
      返回值格式: x.o bar.o
      
    3. 继续优化代码

    #定义变量
    src=$(wildcard ./*.c)
    obj=$(patsubst %.c, %.o, $(src))
    target=app
    
    $(target):$(obj)
            $(CC) $(obj) -o $(target)
    
    %.o:%.c
            $(CC) -c $< -o $@
    
  5. (技巧)rm xxx -f:–force 删除文件,并忽略不存在的文件,从不给出提示。

  6. 在Makefile文件中加入clean,可以删除不需要的.o文件,例如:

clean:
        rm $(obj) -f

在外部使用make clean,否则不会执行,因为make默认执行第一条规则,此外,clean无需生成特定文件,所以在clean上方加.PHONY:clean,生成伪目标,即变为
.PHONY:clean
clean:
        rm $(obj) -f
  1. (技巧)touch xxx新建文件

1.13 GDB调试(1):准备工作

  1. 定义

    • GDB 是由 GNU 软件系统社区提供的调试工具,同 GCC 配套组成了一套完整的开发环境, GDB 是 Linux 和许多类 Unix 系统中的标准开发环境。
    • GDB 主要完成下面四个方面的功能:
      • 启动程序,可以按照自定义的要求随心所欲的运行程序
      • 可让被调试的程序在所指定的调置的断点处停住(断点可以是条件表达式)
      • 当程序被停住时,可以检查此时程序中所发生的事
      • 可以改变程序,将一个 BUG 产生的影响修正从而测试其他 BUG
  2. 准备工作

    • 在为调试而编译时,会关掉编译器的优化选项(-O:这是大O), 并打开调试选项(-g)。另外, -Wall在尽量不影响程序行为的情况下选项打开所有warning,也可以发现许多问题,避免一些不必要的 BUG。 例如:
    gcc -g -Wall program.c -o program  
    
    • -g 选项的作用是在可执行文件中加入源代码的信息,比如可执行文件中第几条机器指令对应源代码的第几行,但并不是把整个源文件嵌入到可执行文件中,所以在调试时必须保证 gdb 能找到源文件
  3. GDB 命令 – 启动、退出、查看代码

    • 启动:gdb 可执行程序 ,退出:quit/q
    • 给程序设置参数:set args 10 20, 获取设置参数:show args
    • GDB 使用帮助 :help

1.14 GDB调试(2): 启动、退出、查看代码

  1. (技巧)vim显示行号::set nu
  2. 查看当前源代码文件
进入gdb,输入:
list/l  (默认位置即从main函数的第1行显示到第10行)
按enter默认执行上一条命令
list/l 行号   (该行号显示在正中间)
list/l 函数名   (该函数显示在中间)
  1. 查看非当前文件代码
list/l 文件名:行号
list/l 文件名:函数名
  1. 查看/设置显示的行数
show list/listsize
set list/listszie 行数

1.15 GDB调试(3):断点操作

  1. 设置断点(将断点设置在这一行的开始,即如果程序走到这里还未执行该行)
break/b 行号
break/b 函数名
break/b 文件名:行号
break/b 文件名:函数名
  1. 查看断点信息
info/i break/b 
  1. 删除断点
delete/del/d 断点编号
  1. 设置断点无效/有效
disable 断点编号,这条命令可从查看断点信息的Enb字段查看
enable 断点编号
  1. 设置条件断点(一般是循环位置)
break/b 10 if i==5,即在第10行加条件断点

1.16 GDB调试(4): 调试命令

  1. 断点在gdb关闭后会消失
  2. 运行GDB程序
start 程序会停在第一行,不会继续执行
run 遇到断点才停,如果没有断点则一次性执行完,经常使用run而非start
  1. 继续运行,直到下一个断点
c/continue
  1. 向下执行一行(不会进入函数体)
n/next
  1. 变量操作
p/print 变量名(打印变量值)
ptype 变量名(打印变量类型)
  1. 向下单步调试(遇到函数进入函数体)
s/step
finish(跳出函数体)
  1. 自动变量操作
display 变量名(自动打印指定变量的值)
i/info display,查看自己设置的自动变量
undisplay 编号,删除自动变量
  1. 其他操作
set var 变量名=变量值 (循环中用的较多)
until (跳出循环)

1.17 标准C库IO函数和Linux系统IO函数对比

  1. IO的定义:从内存的角度进行分析,如果把文件内容写到内存则称为I,把内存的内容写到文件则成为O
  2. 标准C库IO
    • 标准C库IO可以跨平台属于第三方库,不属于操作系统,在使用时会调用平台的API
    • 标准C库IO与Linux系统IO是调用与被调用的关系
    • 标准C库比较高级,效率较高(存在缓冲区),而Linux的IO偏向底层,效率低(与外存/磁盘交换数据耗时间)
    • Linux系统IO常用于网络通信,提高通信效率

image-20220806203022895

image-20220806203803628

  1. (技巧)man 3 fopen,标准C库IO在第3章,这句话可以看到fopen的代码

1.18 虚拟地址空间

具体原理见博客地址

image-20220807194300648

1.19 文件描述符

  1. 程序是一个文件,不占用内存空间,只有程序执行时才会变为进程,并占用内存空间

image-20220807195620635

1.20 open打开文件

  1. (技巧)在命令行输入:man 2 open,可以看open的源代码,Linux标准库在第2章。(打印错误信息perror函数属于c标准库中的函数,在第3章)
  2. (技巧)linux有错误一般返回-1
  3. (技巧)在源文件中输入/内容,表示查找内容
  4. 原理

image-20220807214527435

  1. 例子

image-20220807215157671

1.21 open创建新文件

  1. 原理(注:图片中操作权限的描述有点错误,那10位中的第一位不一定是-,它代表文件类型

image-20220808205220654


  1. 例子

image-20220808205416182


1.22 read、write函数

  1. 原理
/*  
    #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
*/
  1. 例子
#include <unistd.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

int main() {
    // 1.通过open打开english.txt文件
    int srcfd = open("english.txt", O_RDONLY);
    if(srcfd == -1) {
        perror("open");
        return -1;
    }
    // 2.创建一个新的文件(拷贝文件)
    int destfd = open("cpy.txt", O_WRONLY | O_CREAT, 0664);
    if(destfd == -1) {
        perror("open");
        return -1;
    }
    // 3.频繁的读写操作
    char buf[1024] = {0};
    int len = 0;
    while((len = read(srcfd, buf, sizeof(buf))) > 0) {
        write(destfd, buf, len);
    }
    // 4.关闭文件
    close(destfd);
    close(srcfd);
    return 0;
}

1.23 lseek函数

  1. 原理
/*  
    标准C库的函数
    #include <stdio.h>
    int fseek(FILE *stream, long offset, int whence);

    Linux系统函数
    #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, 110b, 增加了100个字节
        lseek(fd, 100, SEEK_END)
        注意:需要写一次数据

*/
  1. 例子
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>

int main() {

    int fd = open("hello.txt", O_RDWR);

    if(fd == -1) {
        perror("open");
        return -1;
    }

    // 扩展文件的长度
    int ret = lseek(fd, 100, SEEK_END);
    if(ret == -1) {
        perror("lseek");
        return -1;
    }

    // 写入一个空数据
    write(fd, " ", 1);

    // 关闭文件
    close(fd);

    return 0;
}

1.24 stat、lstat函数

  1. stat命令

image-20220816160545074

  1. 原理
/*
    #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 lstat(const char *pathname, struct stat *statbuf);
        参数:
            - pathname:操作的文件的路径
            - statbuf:结构体变量,传出参数,用于保存获取到的文件的信息
        返回值:
            成功:返回0
            失败:返回-1 设置errno

*/
  1. stat结构体

image-20220816161552703

  1. st_mode变量

image-20220816161907809

  1. 例子
#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);  // 获取结构中的st_size变量


    return 0;
}
  1. 在命令行创建软链接:ln -s a.txt b.txt,通过a.txt创建了软链接b.txt

命令行里没有lstat,但linux库函数有。如果用的stat查看b.txt的大小,则显示a.txt的大小;如果用lstat显示的就是b.txt的大小了。即stat保存的是原文件的信息,lstat保存的是软链接文件信息。

1.25 模拟实现ls -l命令


#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>

// 模拟实现 ls -l 指令
// -rw-rw-r-- 1 nowcoder nowcoder 12 12月  3 15:48 a.txt
/* argv[]为保存命令行参数的字符串指针,第0个参数是程序的全名,以后的参数为命令行后面跟的用户输入的参数。指针数组的长度即为参数个数argc。数组元素初值由系统自动赋予。*/ 
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] = 's';
            break;
        case S_IFIFO:
            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;

    // 文件所有者(getpwuid函数在pwd.h头文件中)
    char * fileUser = getpwuid(st.st_uid)->pw_name;

    // 文件所在组(getgrgid函数在grp.h头文件中)
    char * fileGrp = getgrgid(st.st_gid)->gr_name;

    // 文件大小
    long int fileSize = st.st_size;

    // 获取修改的时间(ctime在time.h头文件中)
    char * time = ctime(&st.st_mtime);

    char mtime[512] = {0};
    strncpy(mtime, time, strlen(time) - 1);  // 将time内容除最后一个换行拷贝到mtime中

    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;
}

1.26 文件属性操作函数

  1. access函数(判断权限)
/*
    #include <unistd.h>
    int access(const char *pathname, int mode);
        作用:判断某个文件是否有某个权限,或者判断文件是否存在
        参数:
            - pathname: 判断的文件路径
            - mode:
                R_OK: 判断是否有读权限
                W_OK: 判断是否有写权限
                X_OK: 判断是否有执行权限
                F_OK: 判断文件是否存在
        返回值:成功返回0, 失败返回-1
*/

#include <unistd.h>
#include <stdio.h>

int main() {

    int ret = access("a.txt", F_OK);
    if(ret == -1) {
        perror("access");
    }

    printf("文件存在!!!\n");

    return 0;
}
  1. chmod函数(修改权限)
/*
    #include <sys/stat.h>
    int chmod(const char *pathname, mode_t mode);
        修改文件的权限
        参数:
            - pathname: 需要修改的文件的路径
            - mode:需要修改的权限值,八进制的数
        返回值:成功返回0,失败返回-1

*/
#include <sys/stat.h>
#include <stdio.h>
int main() {

    int ret = chmod("a.txt", 0777);

    if(ret == -1) {
        perror("chmod");
        return -1;
    }

    return 0;
}
  1. chown函数(修改用户和组)
/*
#include <unistd.h>
int chown(const char *pathname, uid_t owner, gid_t group);
*/

在命令行输入vim /etc/passwd查看用户名的id,输入vim /etc/group查看组的id

sudo useradd chaopro2可以新建用户chaopro2

id chaopro可以查看用户id信息

  1. truncate函数(文件尺寸的缩减与扩展)
/*
    #include <unistd.h>
    #include <sys/types.h>
    int truncate(const char *path, off_t length);
        作用:缩减或者扩展文件的尺寸至指定的大小
        参数:
            - path: 需要修改的文件的路径
            - length: 需要最终文件变成的大小
        返回值:
            成功返回0, 失败返回-1
*/

#include <unistd.h>
#include <sys/types.h>
#include <stdio.h>

int main() {

    int ret = truncate("b.txt", 5);

    if(ret == -1) {
        perror("truncate");
        return -1;
    }

    return 0;
}

1.27 目录操作函数

  1. mkdir函数(创建目录)
/*
    #include <sys/stat.h>
    #include <sys/types.h>
    int mkdir(const char *pathname, mode_t mode);
        作用:创建一个目录
        参数:
            pathname: 创建的目录的路径
            mode: 权限,八进制的数
        返回值:
            成功返回0, 失败返回-1
*/

#include <sys/stat.h>
#include <sys/types.h>
#include <stdio.h>

int main() {

    int ret = mkdir("aaa", 0777);  // 最终并不是0777权限,有掩码,可以在man 2 mkdir查看文档

    if(ret == -1) {
        perror("mkdir");
        return -1;
    }

    return 0;
}
  1. rmdir(删除目录)
  2. rename(重命名)
/*
    #include <stdio.h>
    int rename(const char *oldpath, const char *newpath);

*/
#include <stdio.h>

int main() {

    int ret = rename("aaa", "bbb");

    if(ret == -1) {
        perror("rename");
        return -1;
    }

    return 0;
}
  1. chdir函数(修改进程的工作目录)、getcwd函数(获取当前工作目录)
/*

    #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;
}

1.28 目录遍历函数

  1. opendir函数(打开目录)、readdir函数(读取目录中的数据)、closedir函数(关闭目录)
/*
    // 打开一个目录
    #include <sys/types.h>
    #include <dirent.h>
    DIR *opendir(const char *name);
        参数:
            - name: 需要打开的目录的名称
        返回值:
            DIR * 类型,理解为目录流
            错误返回NULL


    // 读取目录中的数据
    #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[]) {

    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;

    // 记录普通文件的个数
    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;
}
  1. dirent结构体(readdir函数的返回值)

image-20220817103117585

1.29 dup、dup2函数

  1. dup函数(复制一个新的文件描述符)
/*
    #include <unistd.h>
    int dup(int oldfd);
        作用:复制一个新的文件描述符
        fd=3, int fd1 = dup(fd),
        fd指向的是a.txt, fd1也是指向a.txt
        从空闲的文件描述符表中找一个最小的,作为新的拷贝的文件描述符


*/

#include <unistd.h>
#include <stdio.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>

int main() {

    int fd = open("a.txt", O_RDWR | O_CREAT, 0664);

    int fd1 = dup(fd);

    if(fd1 == -1) {
        perror("dup");
        return -1;
    }

    printf("fd : %d , fd1 : %d\n", fd, fd1);

    close(fd);

    char * str = "hello,world";
    int ret = write(fd1, str, strlen(str));
    if(ret == -1) {
        perror("write");
        return -1;
    }

    close(fd1);

    return 0;
}
  1. dup2函数(重定向文件描述符)
/*
    #include <unistd.h>
    int dup2(int oldfd, int newfd);
        作用:重定向文件描述符
        oldfd 指向 a.txt, newfd 指向 b.txt
        调用函数成功后:newfd 和 b.txt 做close(newfd和b.txt无关了), newfd 指向了 a.txt
        oldfd 必须是一个有效的文件描述符
        oldfd和newfd值相同,相当于什么都没有做
*/
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>

int main() {

    int fd = open("1.txt", O_RDWR | O_CREAT, 0664);
    if(fd == -1) {
        perror("open");
        return -1;
    }

    int fd1 = open("2.txt", O_RDWR | O_CREAT, 0664);
    if(fd1 == -1) {
        perror("open");
        return -1;
    }

    printf("fd : %d, fd1 : %d\n", fd, fd1);

    int fd2 = dup2(fd, fd1);
    if(fd2 == -1) {
        perror("dup2");
        return -1;
    }

    // 通过fd1去写数据,实际操作的是1.txt,而不是2.txt
    char * str = "hello, dup2";
    int len = write(fd1, str, strlen(str));

    if(len == -1) {
        perror("write");
        return -1;
    }

    printf("fd : %d, fd1 : %d, fd2 : %d\n", fd, fd1, fd2);

    close(fd);
    close(fd1);

    return 0;
}

1.30 fcntl函数

/*

    #include <unistd.h>
    #include <fcntl.h>

    int fcntl(int fd, int cmd, ...);
    参数:
        fd : 表示需要操作的文件描述符
        cmd: 表示对文件描述符进行如何操作
            - F_DUPFD : 复制文件描述符,复制的是第一个参数fd,得到一个新的文件描述符(返回值)
                int ret = fcntl(fd, F_DUPFD);

            - F_GETFL : 获取指定的文件描述符文件状态flag
              获取的flag和我们通过open函数传递的flag是一个东西。

            - F_SETFL : 设置文件描述符文件状态flag
              必选项:O_RDONLY, O_WRONLY, O_RDWR 不可以被修改
              可选性:O_APPEND, O_NONBLOCK
                O_APPEND 表示追加数据
                NONBLOK 设置成非阻塞
        
        阻塞和非阻塞:描述的是函数调用的行为。
*/

#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>

int main() {

    // 1.复制文件描述符
    // int fd = open("1.txt", O_RDONLY);
    // int ret = fcntl(fd, F_DUPFD);

    // 2.修改或者获取文件状态flag
    int fd = open("1.txt", O_RDWR);
    if(fd == -1) {
        perror("open");
        return -1;
    }

    // 获取文件描述符状态flag
    int flag = fcntl(fd, F_GETFL);
    if(flag == -1) {
        perror("fcntl");
        return -1;
    }
    flag |= O_APPEND;   // flag = flag | O_APPEND

    // 修改文件描述符状态的flag,给flag加入O_APPEND这个标记
    int ret = fcntl(fd, F_SETFL, flag);
    if(ret == -1) {
        perror("fcntl");
        return -1;
    }

    char * str = "nihao";
    write(fd, str, strlen(str));

    close(fd);

    return 0;
}

函数

/*

    #include <unistd.h>
    #include <fcntl.h>

    int fcntl(int fd, int cmd, ...);
    参数:
        fd : 表示需要操作的文件描述符
        cmd: 表示对文件描述符进行如何操作
            - F_DUPFD : 复制文件描述符,复制的是第一个参数fd,得到一个新的文件描述符(返回值)
                int ret = fcntl(fd, F_DUPFD);

            - F_GETFL : 获取指定的文件描述符文件状态flag
              获取的flag和我们通过open函数传递的flag是一个东西。

            - F_SETFL : 设置文件描述符文件状态flag
              必选项:O_RDONLY, O_WRONLY, O_RDWR 不可以被修改
              可选性:O_APPEND, O_NONBLOCK
                O_APPEND 表示追加数据
                NONBLOK 设置成非阻塞
        
        阻塞和非阻塞:描述的是函数调用的行为。
*/

#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>

int main() {

    // 1.复制文件描述符
    // int fd = open("1.txt", O_RDONLY);
    // int ret = fcntl(fd, F_DUPFD);

    // 2.修改或者获取文件状态flag
    int fd = open("1.txt", O_RDWR);
    if(fd == -1) {
        perror("open");
        return -1;
    }

    // 获取文件描述符状态flag
    int flag = fcntl(fd, F_GETFL);
    if(flag == -1) {
        perror("fcntl");
        return -1;
    }
    flag |= O_APPEND;   // flag = flag | O_APPEND

    // 修改文件描述符状态的flag,给flag加入O_APPEND这个标记
    int ret = fcntl(fd, F_SETFL, flag);
    if(ret == -1) {
        perror("fcntl");
        return -1;
    }

    char * str = "nihao";
    write(fd, str, strlen(str));

    close(fd);

    return 0;
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值