掘金 后端 ( ) • 2024-06-29 16:43

[TOC]

Linux环境基础开发工具的使用

一、Linux软件包管理器——yum

1.Linux下软件安装的方式

在Linux下,软件安装有以下三种方式:

  1. ==源码安装==:获取相应的软件的源代码->编译成二进制文件->安装
  2. ==rpm安装==:rpm是由红帽公司开发的软件包管理方式,使用rpm我们可以方便的进行软件的安装、查询、卸载、升级等工作。但是rpm软件包之间的依赖性问题往往会很繁琐,尤其是软件是由多个rpm包组成时。
  3. ==yum安装==:(全称为 Yellow dog Updater,Modified)是一个在Fedora和RedHat以及SUSE中的Shell前端软件包管理器。基于RPM包管理,能够从指定的服务器自动下载RPM包并且安装,可以自动处理依赖性关系,并且一次安装所有依赖的软件包,无须繁琐地一次次下载、安装。==(最常用)==

2.如何查看Linux下的软件包

​通过yum list来查看所有的软件包 ,指令sudo yum list

image-20240110154109455

​软件包名称:主版本号.次版本号.源程序发行号-软件包的发行号.主机平台.cpu架构. “x86_64”后缀表示64位系统的安装包,”i686“后缀表示32位系统安装包。选择包时要和系统匹配。”el7“表示操作系统发行版的版本,”el7“表示的是centos7/redhat7。”el6”表示centos6/redhat6。最后一列,base表示的是“软件源”的名称,类似于“小米应用商店”,“华为应用商店”这样的概念。

​也可以通过管道加grep过滤来想要查找的软件,以查找sl.x86_64软件为例: image-20240110155101513

3.利用yum来安装相应的软件

​指令:sudo yum install 软件名;我们这里使用的是普通用户,所以要加sudo;执行指令后需要输入普通用户的密码,然后输入“y”确认安装,当出现complete时,表明安装成功;

sudo yum install sl.\x86_64  //这种需要输入y来进行确认
//或
sudo yum install -y sl.\x86_64  //这里带 -y 可以直接安装

​软件的运行效果如下:(直接输入sl就可以运行,是一个可以运动的小火车)

image-20240110155626043

4.利用yum来进行软件的卸载

​指令:sudo yum remove 软件名 ;同样需要输入密码,输入‘y’确认卸载,当出现complete时表明卸载成功;

​例如:sudo yum remove sl

5.如何实现云服务器与本地机器间的文件互传

​当我们想要将自己电脑中的文件传入云服务器时,Linux也支持拖拽式上传。

image-20240110161826789

​接下来介绍两个指令:rz和sz

rz:运行该指令会弹出一个文件选择窗口,从本地选择文件长传到服务器;一般小文件我们都是通过rz -y来进行上传文件,除此之外还可以用rz -E。 rz -y和rz -E的共同点和区别: 共同点: 都可以把文件上传到Linux中 区别: rz -y:把文件上传到Linux中,如果有相同的文件名的文件,会将其覆盖。 rz -E:把文件上传到Linux中,如果有相同文件名的文件,不会将其覆盖,而是会在所上传文件后面加上.0,两个文件都会存在与此目录中,再次上传则会在文件名后加上.1,以此类推。 image-20240110162443432

sz:将选定的文件发送到本地机器 指令:sz 文件名 image-20240110162630522

​可以将第三点图.vsdx 加载到本地机器指定的文件夹中 以上两个指令如果出现以下的情况:

# sz
-bash: sz: command not found
# rz
-bash: /usr/bin/rz: No such file or directory

==就需要安装lrzsz相关的软件包==

6.注意事项

关于yum的所有操作必须保证主机(虚拟机)网络畅通!!! 可以通过ping 指令验证 ping www.baidu.com image-20240110163128388

二、Linux编辑器——vim的使用

​Vim是一个文本编辑器。Vim具有代码编译、补全以及错误跳转等编程功能,在程序员中被广泛使用。

1.vim常见的三种模式

  1. 命令模式(Normal mode):

    使用Vim编辑文件时,==默认处于命令模式==。在此模式下,可以使用上、下、左、右键或者k(上)、j(下)、h(左)、l(右)命令进行光标移动,还可以对文件内容进行复制、粘贴、替换、删除等操作。

  2. 输入模式(insert mode):

    在输入模式下可以对文件执行写操作,类似在windows的文档中输入内容。进入输入模式的方法是输入i、a、o等插入命令,编写完成后按Esc键即可返回命令模式。

  3. 编辑模式(Command mode):

    如果要保存、查找或者替换一些内容等,就需要进入编辑模式。编辑模式的进入方法为:在命令模式下按“ :”键,Vim窗口的左下方会出现一个“ : ”符号,这时就可以输入相关的指令进行操作了。指令执行后会自动返回命令模式。

2.vim键盘图

image-20240110165301560

3.vim三种基本模式间的切换

​当我们创建了文件并使用vim指令进行文本编辑时,默认进入的都是命令模式,只有在命令模式下输入a或i或o指令后,才可以进入插入模式,进行相应的文本编辑;文本编辑结束后,就需要保存起来,我们必须切换到命令模式下输入shift + : 进入底行模式,输入wq指令进行保存; ​注意:我们并不能从底行模式直接进入插入模式,反之亦然;如果当前并不知道处于生命模式下直接按Esc就一定是在命令模式下。 image-20240110165723011

命令模式切换到插入模式可以三种方式: i:输入i后进入插入模式,光标处于文本编辑的起始位置,进行插入; a:输入a后进行插入模式,光标处于文本编辑器起始位置的下一个位置,进行插入; o:输入0后进入插入模式,光标会在新起一行的位置,进行插入; 命令模式切换到底行模式: 输入shift + : 插入模式或底行模式切换到命令模式: 输入Esc

4.vim的命令模式下相关命令

  1. 光标移动

    按键操作 功能描述 h 左移一个位置 j 向下一行 k 向上一行 l 右移一个位置 G 移动到文章的最后 $ 移动到光标所在行的”行尾“ ^ 移动到光标所在行的”行首“ w 光标跳到下个字的开头 e 光标跳到下个字的字尾 b 光标回到上个字的开头 #I 光标移到该行的第#个位置,入5I gg 进入到文本开始 shift+g 进入文本末端 ctrl+b 屏幕往”后“移动一页 ctrl+f 屏幕往”前“移动一页 ctrl+u 屏幕往”前“移动半页 ctrl+d 屏幕往”前“移动半页
  2. 删除

    按键操作 功能描述 x 每按一次,删除光标所在位置的一个字符,支持nx #x 例如,[6x]表示删除光标所在位置的”后面(包括自己在内)”6个字符 X 大写的X,每按一次,删除光标所在位置的“前面”一个字符 #X 例如,[20X]表示删除光标所在位置的“前面”20个字符 dd 剪切光标所在行,支持ndd #dd 从光标所在行开始删除#行
  3. 复制

    按键操作 功能描述 yw 将光标所在之处到字尾的字符复制到缓冲区中 #yw 复制#个字到缓冲区 yy 复制光标所在行到缓冲区 #yy 例如:[6yy]表示拷贝从光标所在的该行“往下数”6行文字 p 将缓冲区内的字符贴到光标所在的位置。注意:所有与“y”有关的复制命令都必须与“p”配合才能完成复制与粘贴的功能
  4. 替换

    按键操作 功能描述 r 替换光标所在处的字符,支持nr R 替换光标所到之处的字符,直到按下[ESC]键为止
  5. 撤销

    按键操作 功能描述 u 如果您误执行一个命令,可以马上按下[u],回到上一个操作。按多次“u”可以执行多次恢复 ctrl+r 撤销的恢复
  6. 更改

    按键操作 功能描述 cw 更改光标所在处的字到字尾处 c#w 例如,[c3w]表示更改3个字
  7. 大小写切换

    按键操作 功能描述 ~ 完成光标所在位置向后进行大小写切换 n~ 完成光标所在位置向后n个字符进行大小写切换
  8. 跳转至指定行

    按键操作 功能描述 ctrl+g 列出光标所在行的行号 #G 例如,[15G],表示移动光标至文章的第15行行首

5.vim底行模式下相关的命令

​在使用底行模式之前,请记住先按[ESC]键确定您已经处于正常模式,再按[ : ]冒号即可进入底行模式

  1. 行号设置

    set nu      //调出行号
    set nonu    //取消行号
    
  2. 跳转到文件中某一行

    「#」:「#」号表示一个数字,在冒号后输入一个数字,再按回车键就会跳到该行了,如输入数字15,再回车,就会跳到文章的第15行。
    
  3. 查找字符

    「/关键字」: 先按「/」键,再输入您想寻找的字符,如果第一次找的关键字不是您想要的,可以一直按「n」会往后寻找到您要的关键字为止。
    「?关键字」:先按「?」键,再输入您想寻找的字符,如果第一次找的关键字不是您想要的,可以一直按「n」会往前寻找到您要的关键字为止
    
  4. 保存与退出

    w:保存文件
    q:退出vim
    !:强制
    w! q! wq!
    
  5. 执行命令

    !+指令:可以在不退出vim的情况下进行指令操作;
    
  6. 分屏操作

    vs 文件名:可以实现分屏,进行多个文件编辑;
    ctrl+w+w:屏幕切换(当前底行模式下输入的命令并执行只与当前光标所在文件有关,和鼠标点击哪个文件无关)
    

6.vim的配置

​新用户在使用vim编写代码时并不像在VS下有自动缩进,自动补齐,代码提示,语法高亮等;需要用户自己去配置

1.配置文件的位置

  1. 在目录/etc/下面,有个名为vimrc的文件,这是系统中公有的vim配置文件,对所有用户都有效
  2. 而在每个用户的主目录下,都可以自己创建私有的配置文件,命名为:”.vimrc”。例如,/root目录下,通常已经存在一个.vimrc文件,如果不存在,则创建之。
  3. 切换用户成为自己执行su,进入自己的主工作目录,执行cd ~
  4. 打开自己目录下的.vimrc文件,执行 vim .vimrc

2.vim配置方法

  1. 采用手动配置:在普通用户下创建一个.vimrc文件,执行vim .vimrc,例如:设置行号(set nu)、设置语法高亮(syntax on);保存并退出,简单测试一下vim test.c 此时行号和语法高亮都能正常显示。虽然可行,但是不推荐这样做,我们可以采用第二种做法;

  2. vim的配置相对比较复杂,某些配置还需要使用到插件,我们可以直接执行下面的代码,curl -sLf https://gitee.com/HGtz2222/VimForCpp/raw/master/install.sh -o ./install.sh && bash ./install.sh 需要输入root密码,等待片刻后,手动执行source ~/.bashrc 即可;最后就可以看到和VS差不多的功能了

    image-20240110215113557

三、Linux编辑器——gcc/g++的使用

1.gcc和g++的使用

​在Linux中,gcc是C的编译器,g++是C++的编译器;一个程序能够执行起来,一般需要经过四个步骤:预处理、编译、汇编和链接。以下图程序为例,对这四个阶段进行在Linux中的操作进行介绍。

#include <stdio.h>
#define NUM  100                          
int main() {
   printf("hello vim\n");
   //printf("hello vim\n");
   //printf("hello vim\n");
   //printf("hello vim\n");
   //printf("hello vim\n");
   printf("hello vim\n");
   printf("NUM = %d\n",NUM);
   return 0;
}

1.预处理

​在预处理阶段主要负责的是头文件的展开、去掉注释、宏替换、条件编译等。以#号开头的是预处理指令:#define #if #include ……..此阶段产生【.i文件】 ​在Linux中,如果想要看到预处理后的结果,可以执行下面的指令: ​gcc -E mytest.c -o test.i -E:只激活预处理,这个不生成文件,你需要把它重定向到一个输出文件里面(xx.i文件) -o:文件输出到文件(将mytest.c文件输出到test.i文件) image-20240110220855456

2.编译

​此阶段完成语法和语义分析,然后生成中间代码,此中间代码是汇编代码,但是还不可执行,gcc编译的中间文件是[.s]文件。在此阶段会出现各种语法和语义错误,特别要小心未定义的行为,这往往是致命的错误。 ​在Linux中,当预处理完生成了test.i文件后,想看到编译产生的汇编代码,可以执行下面的指令: ​gcc -S test.i -o test.s -S:编译到汇编语言不进行汇编和链接 -o:文件输出到文件 image-20240110221526401

​预处理后的代码相比之前干净了很多,但还是C语言,经过编译后,转换成了汇编代码。

3.汇编

​此阶段主要完成将汇编代码翻译成机器码指令,并将这些指令打包形成可重定位的目标文件,[.o]文件,是二进制文件。此阶段由汇编器完成。 ​在Linux下,可以执行下面这个指令: ​gcc -c test.s -o test.o -c:编译到目标代码 -o:文件输出到文件 image-20240110222036323

​将汇编代码转换为了程序员看不懂,电脑能看懂的二进制文件

4.链接

​此阶段完成文件中使用的各种函数跟静态库和动态库的连接,并将它们一起打包合并形成目标文件,即可执行文件。此阶段由链接器完成。 ​gcc test.o -o test

2.静态库与动态库

静态库: 一般扩展名为(.a或者.lib) 静态库在编译的时候会直接整合到目标程序中(理解为拷贝),所以利用静态函数库编译成的文件会比较大,这类函数库最大的优点就是编译成功的可执行文件可以独立运行,而不再需要向外部要求读取函数库的内容;但是从升级难易度来看明显没有优势,如果函数库升级,需要重新编译 动态库: 动态函数库的扩展名一般为(.so或.dll) 与静态库被整个整合到程序中不同,动态库在编译的时候,在程序里只有一个“指向”的位置而已,也就是说当可执行文件需要使用到函数库的机制时,程序才会去读取函数库来使用;也就是说可执行文件无法单独运行。这样从产品功能升级角度方便升级,只要替换对应的动态库即可,不必重新编译整个可执行文件。

1.静态链接与动态链接的区别

==一般gcc和g++默认都采用的是动态链接== 静态链接的过程就已经把要链接的内容以及链接到了可生成的可执行文件中,就算你再去把静态库删除也不会影响可执行程序的执行; 动态链接这个过程却没有把内容链接进去,而是在执行的过程中,再去找到链接的内容,生成的可执行文件中并没有要链接的内容,所以当你删除动态库时,可执行程序就不能运行。 所以总的来说,动态链接生成的可执行文件要比静态链接生成的文件要小一些。 通过file 文件名 可以查看文件信息 image-20240111084314313

image-20240111084917438 很明显的看出静态链接生成的可执行文件的大小比动态链接的要大的多

2.如何实现静态链接(含安装)

​通过这条指令 gcc mytest.c -o test1 -static 如果出现下面这种情况的报错,是因为在新版本的Linux系统下安装glibc-devel、glibc和gcc-c++时,都不会安装libc.a.只安装libc.so.所以当使用-static时,libc.a不能使用。只能报找不到libc了。 ​/usr/bin/ld: cannot find -lc

安装glibc-static
sudo yum install glibc-static

3.gcc/g++常用选项

选项 功能 -E 只激活预处理,这个不生成文件,你需要把它重定向到一个输出文件里面 -S 编译到汇编语言不进行汇编和链接 -c 编译到目标代码 -o 文件输出到文件 -static 此选项对生成的文件采用静态链接 -g 生成调试信息。GNU调试器可利用该信息 -shared 此选项将尽量使用动态库,所以生成文件比较小,但是需要系统有动态库 -w 不生成任何警告信息 -Wall 生成所有警告信息 -O0 -O1 -O2 -O3 编译器的优化选项的4个级别,-O0表示没有优化,-O1表示缺省值,-O3表示优先级别最高

四、Linux编辑器——gdb的使用

1.gdb的基本概念

​Linux包含了一个叫gdb的GNU调试程序,gdb是一个用来调试C和C++程序的强力调试器。它使你能在程序运行时观察程序的内部结构和内存的使用情况。 1.程序的发布方式有两种,debug模式和release模式 ​debug模式:只有在debug下才可以进行调试,因为该模式下加入了debug调试信息; ​release模式:去掉了这些调试信息,是不可以被调试了 2.Linux下gcc/g++出来的二进制程序,默认是release模式 3.要使用gdb调试,必须在源代码生成二进制程序的时候,加上-g选项 image-20240111091047384

​从上图中可以发现debug版本和release版本下生成的可执行程序,文件大小是不一样的,也正说明了debug确实加入了一些调试信息

2.gdb指令

1.常见指令汇总

指令 简写 功能 list l 显示代码,每次列出10行,然后接着上次位置往下列 run r 运行程序 next n 单条执行(相当于VS下的F10) step s 进入函数调用(相当于VS下的F11) break b 在某一行或某个函数开头设置断点 info break i b 查看断点信息 finish 执行到当前函数返回,然后停下来等待命令 print p 打印变量值或表达式的值 set var 修改变量的值 continue c 从当前位置开始连续执行程序,遇到断点停下 delete breakpoints delete 删除所有断点 delete breakpoints n delete n 删除序号为n的断点,可以多个删除 disable breakpoints 禁用断点 enable breakpoints 启用断点 display 跟踪查看一个变量,每次停下来都显示他的值 undisplay 取消对先前设置的那些变量的跟踪 until 跳转到指定行 breaktrace bt 查看各级函数调用及参数 info locals i locals 查看当前栈帧局部变量的值 quit q 退出gdb

2.指令操作演示

​通过下面的代码来演示Linux下调试的技巧

#include <stdio.h>
int fun(int p){
  int i = 0;
  int sum = 0;
  for(i = 0;i <= p;i++) {
    sum += i;
  }
  return sum;
}
int main() {
  int x = 100;
  int ret = 0;
  ret = fun(x);
  printf("ret = %d\n",ret);
  return 0;
}

查看源代码——list 行号 和 list 函数名,可简写为 l 行号l 函数名

image-20240111094558242

image-20240111095421061

image-20240111095534756

调试程序:run、next、step配合断点相关的指令

image-20240111100917034

image-20240111102804242

finish、continue和until的区别

在函数执行过程中,finish是用来结束当前函数

image-20240111110511287

在函数执行过程中,coninue是用来直接到达下一个断点(在有断点的情况下)

image-20240111111025087

until是用来跳转至某一行的

image-20240111111232413

查看变量:print、display、以及undisplay

image-20240111111811515

image-20240111111925643

退出调试是quit,可简写为q

五、Linux项目自动化构建工具——make/Makefile

1.make/Makefile的背景

  1. 会不会写makefile,从一个侧面说明了一个人是否具备完成大型工程的能力。
  2. 一个工程中的源文件不计数,其按类型、功能、模块分别放在若干个目录中,makefile定义了一系列的规则来指定,哪些文件需要先编译,哪些文件需要后编译,哪些文件需要重新编译,甚至于进行更复杂的功能操作。、
  3. makefile带来的好处就是——“自动化编译”,一旦写好,只需要一个make命令,整个工程完全自动编译,极大的提高了软件开发的效率
  4. make是一个命令工具,是一个解释makefile中指令的命令工具,一般来说,大多数的IDE都有这个命令,比如:Delphi的make,Visual C++的nmake,Linux下GNU的make。可见,makefile都成为了一种在工程方面的编译方法。
  5. make是一条指令,makefile是一个文件,两个搭配使用,完成项目自动化构建。

2.依赖关系和依赖方法

​想要生成可执行程序,需要经过预处理、编译、汇编和链接,他们之间存在依赖关系和依赖方法,如下图所示: image-20240111115244919

​从上图可以发现,想要得到可执行程序,我们首先要得到test.o文件,要得到test.o文件就要得到test.s文件,以此类推,只要我们写好了源代码,通过这样的依赖关系和依赖方法,最终就能得到可执行程序。 ​当我们有很多文件时,就需要用到make/Makefile来对这些文件的依赖关系以及依赖方法进行编写,就不需要每次gcc编译每个程序。当然上图中的关系是细分到每一步的,我们在编写的过程中不需要细分到每一步。

3.如何编写Makefile文件

1.创建Makefile文件

make是依赖makefile的,要运行make,当前目录下就必须有makefile这个文件。 makefile是阐述依赖关系和依赖方法的。 makefile文件命名只允许首字母大写或小写,其余都是小写

2.编写Makefile文件

​在编写Makefile文件时,先表明依赖关系(如A:B表示A依赖于B),紧接着书写依赖方法 image-20240111120429714

以上两张写法都可以,解释以下第二种写法: $@:表示依赖关系中的目标文件,也就是mytest

$^:表示依赖关系中的文件列表,也就是mytest.c;(不仅仅只有mytest.c可能更多)

3.效果展示

image-20240111121056142

4.make的原理

  1. make会在当前目录下找名字叫“Makefile”或者“makefile”的文件。
  2. 如果找到,它会找文件中的第一个目标文件(target),在上面的例子中,他会找到“test”这个文件,并把这个文件作为最终的目标文件。
  3. 如果test文件不存在,或是test所依赖的test.o文件的文件修改时间要比test这个文件新(可以用touch测试),那么,他就会执行后面所定义的命令来生成test这个文件
  4. 如果test所依赖的test.o文件不存在,那么make会在当前文件中找目标为test.o文件的依赖性,如果找到则再根据那一个规则生成test.o文件。(这有点像一个堆栈的过程)
  5. 当然,你的C文件和H文件是存在的啦,于是make会生成test.o文件,然后再用test.o文件去生成make的终极任务,也就是执行文件test了。
  6. 这就是整个make的依赖性,make会一层又一层地去找文件的依赖关系,直到最终编译出第一个目标文件。
  7. 在找寻的过程中,如果出现错误,比如最后被依赖的文件找不到,那么make就会直接退出,并报错,而对于所定义的错误,或者编译不成功,make根本不理。
  8. make只管文件的依赖性,即,如果在我找了依赖关系后,冒号后面的文件还是不在,那么对不起,我就不工作啦。

5.项目清理

​项目清理类似于VS下的清理解决方案,也就是将之前生成的所有文件清理掉,如果我们进行手动清理,那么在有多个文件的情况下,手动清理未免太过麻烦,此时我们可以将项目加入到Makefile文件中。 image-20240111143004967

.PHONY类似于关键字,是用来修饰clean的,它是一个伪目标,表示总数被执行。

image-20240111144019352

问题:为什么在使用Makefile时,只需要输入make;使用clean时,输入却是make clean? ==解析:因为Makefile文件在执行是从上至下的,只输入make,它会默认执行第一个依赖关系==

六、Linux第一个小程序——进度条

1.回车换行的概念

回车(\n):表示将光标移动到文本的行首; 换行(\r):表示将光标移动到下一行,但是相对位置并没有发生改变(列不变)

2.行缓冲区的概念

我们首先观察以下三种不同的程序运行起来有什么不同的现象

#include <stdio.h>
#include <unistd.h>
int main() {  //程序1
   printf("hello Makefile!\n");//带换行
   sleep(3);//休眠3秒
   return 0;
} 
#include <stdio.h>
#include <unistd.h>
int main() {  //程序2
   printf("hello Makefile!");//不带换行
   sleep(3);//休眠3秒
   return 0;
}
#include <stdio.h>
#include <unistd.h>
int main() { //程序3
   printf("hello Makefile!");//不带换行
   fflush(stdout);//刷新缓冲区
   sleep(3);//休眠3秒
   return 0;
}

程序1:立即打印出hello Makefile! 然后换行,休眠3秒后,程序结束; 程序2:未立即打印hello Makefile! 休眠3秒后才打印内容,程序结束; 程序3:立即打印出hello Makefile! 休眠3秒,程序结束; 比较三个程序,我们发现他们打印的时机不一样,这是由于行缓存造成的。 什么是行缓冲? 当输入输出遇到换行符的这类缓冲定义为行缓冲。标准输入和标准输出都是行缓冲。 缓冲区刷新的条件: 1.进程结束 2.遇到\n 3.缓冲区满 4.手动刷新缓冲区fflush(stdout) 5.调用exit(0),但是还可以调用_exit(0),不刷新缓冲区

3.进度条代码

#include <stdio.h>
#include <unistd.h>
#include <string.h>
int main() {
   int i = 0;
   char bar[102];
   memset(bar, 0 ,sizeof(bar));
   const char *lable="|/-\\";//类似于加载的过程
   while(i <= 100 ) {
       printf("[%-100s][%3d%%][%c]\r", bar, i, lable[i%4]);
       fflush(stdout);//刷新缓冲区
       bar[i++] = '#';
       usleep(50000);
   }
   printf("\n");
   return 0;
}

image-20240111145959283