目录
1. yum
1.1 软件的生态
1.2 yum使用
2. vim
4. vim三种模式的更详细命令
5. gcc
6. 重要概念:函数库
7. 动态库与静态库
8. 自动化构建工具:make/Makefile
.PHONY
9. make 与 是否执行
%通识符
生成两个可执行程序
10.练习 ,实现Linux中的进度条
回车和换行
实现进度条:
1. yum
yum是一个软件包管理器。除了yum,还有apt等也能执行相关功能(不同的发型版本使用的不一样)。
Linux中一般有以下三种安装方式:
b用的少,a/c用的多
yum就是在这样一个开源环境下的产物。能产生这样好用的工具,离不开开源OS Linux优秀的生态环境。那什么是一款操作系统的生态环境呢?
1.1 软件的生态
操作系统是否容易获取,是否有论坛,是否有对应的官方手册,是否有足够多的人在你前面踩过坑。并且实现的时候的用户选体,是客户多还是工程师多还是学生多等等.......
应用软件数量和质量:软件生态中包含的应用软件的多样性和质量,这些应用能够满足用户的需求,并推动操作系统的普及和发展。
与周围设备的兼容性:操作系统需要与各种硬件设备兼容,以确保用户能够无缝地使用这些设备。
体系内硬件类型:包括云服务在内的各种硬件类型,它们与操作系统的协同工作能力是软件生态的一部分。
对新生设备和原有设备之间的联系:操作系统需要能够支持新的设备,同时也要与旧设备保持兼容性。
收入来源:软件生态还包括操作系统的商业模型,如通过应用商店等方式带来的收入。
开放性、多样性、稳定性和可持续性:软件生态系统具有开放性,允许自由交流和沟通;多样性,包含丰富的要素类型;稳定性,能够自我调节保持动态均衡;可持续性,利用自我调节能力持续稳定发展。
生态网络:软件生态系统中存在软件制品依赖网络、利益相关者协作网络和价值网络,这些网络体现了软件生态中的相互关系和互动。
信息量:软件生态系统中包含的信息量,涉及利益相关者、软件制品的属性和状态信息,以及生态网络蕴含的信息
开源能明显的丰富整个生态,后发的软件就算把代码直接抄过来,生态照样无法复制。
1.2 yum使用
yum像是Linux中的“应用商店”,一般yum用于安装时都需要root权限。
yum install -y gcc
(安装gcc)
或者普通用户会选择使用sudo:
sudo yum install -y gcc-c++
(安装g++)
也可以用yum查找相关的安装(在应用市场里找相关的安装包):
$ yum list | grep gcc
这样就能查到很多和gcc有关的可安装的程序:
删除:
$ sudo yum remove lrzsz//删除lrzsz这个应用
执行指令之后会询问Is this OK,成功删除之后会显示一个complete
没有提示的删除选项:-y
-y,避免查询
浅显理解yum是如何完成安装的:
yum通过yum的配置文件(通常存在/etc/yum.repos.d/)来访问各种软件包仓库
其中YUM用来查找和下载软件包的服务器位置叫YUM源(Repository)**
cd进入该文件夹,打开CentOS-Base.repo,可以看到里面全是各种网址。
建议更换成国内的yum源(配置yum源其实是将新下载的yum源放到指定路径中替换老的yum源):
首先转移原有的yum源之后清理缓存
,否则还能在原来的yum源中找到东西。
可以找到一个yum源,然后使用wget下载
然后进入到存放系统配置文件的etc目录,找到CentOS-Base.repo
必须将yum源命名chengcentOs_Base
2. vim
vim是一款多模式的编辑器。不同于vs2022这种IDE(集成开发环境),vim只能进行编辑,而不能编译、调试等。
vim有三种模式:命令模式、插入模式、底行模式。
刚打开vim,默认所处的模式叫命令模式 (也是vim的默认模式)
按下i键,进入插入模式。插入模式可以输入内容。
输入完成之后按下ESC退回到命令模式,再输入:进入底行模式,按下wq退出
命令模式是不能输入内容的,按下的键都被当作命令。插入模式和底行模式都通过ESC 回退到命令模式。底行模式中输入 set nu可以获得行号。
vim中的各种命令及其使用方法(都需要在命令模式下执行):
光标所在的行的整行操作:
按下yy进行复制整行,再按p进行粘贴。撤销操作按下u,剪切是dd(剪了不贴就删除,所以删除整行也是dd,删除单个的是x)
同理,如果想复制光标所在行及其下面的两行(一共复制3行),按下数字键3之后按下yy
光标快速定位操作(围绕g进行):
比如当前按下9+shift+g
跳转的就是第9行。(实现定位)
h j k l
命令模式中,hjkl分别表示左下上右。
一行中更为快速的移动方式:
$移动到行末
^移动到行首 , 行尾与行首也称之为锚点
可以理解为 ^和b是快速向前移动的方法,w和$是快速向后移的方法。
删除:
x可以按照光标位置逐个向后删除。
n+x 删除n个
shift x向前删
n shift x 向前删n个
或者进入插入模式直接按照文本模式删除也行。但是命令模式一般比插入模式快一点。
大小写改变与替换:
shift+~ 直接改变大小写
r(小):替换,也可以r + (想要去替换的字符)
R(大,ctrl+r): 进入替换模式(左下角会出现模式名称,回到命令模式按下ESC)
如果只是单独的字符改变(比如一段代码拼错了一个字符),直接r+想替换掉字符 即可,有大量的文字转换,建议进入REPLACE模式。
进入底行模式之后
搜索的方法:
/
语法:
/想要查找的内容
光标会跳转到查找的地方并且高亮。
然后可以按下n命令(next)来走到下一个满足条件的位置。
4. vim三种模式的更详细命令
插入模式:
按下a、i、o都可以从命令模式进入插入模式
a是光标往后移动一位后进入,i是直接进入,o是光标提行后进入。
底行模式和插入模式之间不支持相互进入。
命令模式中的撤销:
撤销:u
关于u的补充命令 :ctrl+r ,这是对u的撤销,u和ctrl+r结合就完成了下图的这两个标的任务
底行模式中的命令:
底行模式中可以有很多设置相关的指令,有一些是可以直接在vim的配置文件中使用的。
设置行号:
set nu set nonu
保存并退出:
w 保存 w!表示强制写入
q 退出 q!表示强制退出
所以之前的wq就是保存并退出,wq!表示强制写入并退出
Linux指令:
感叹号加上普通的指令,可以暂时退出vim看相关现象。
比如!pwd 或者 !ls -a
或者想直接在vim中编译:
!gcc code.c -o hello.exe
多框模式:
写一份代码的时候通常需要其他人代码的参考或者参考自己写的头文件等,为此我们需要开“多屏”。
底行模式下,vs指令进入多框模式
语法:
vs 想要分屏的其他文件
vs file.txt
光标在哪一个文件中,就是对哪一个文件进行操作
切换光标:ctrl + ww(两次w):
shift+zz可以快捷退出当前操作的文件,但还是建议wq,因为shift+zz对应的是系统的默认退出,默认操作具有不确定性,不一定能对你的更改作出保存或者写入等。
连续注释:
单行注释直接在输入模式//即可。
连续注释:
ctrl+v ,进入视图模式,然后使用j k上下进行选中(或者使用n(数字)+shift+g)
然后shift+i进入输入模式,按下双斜杠,再两次esc,
取消注释也可以类似的方法:
vim的配置:
在当前用户的家目录下,创建配置文件 .vimrc(注意包括前面的这个点!)
再打开,然后写入set nu
每次vim启动都会先去读取当前目录下的.vimrc
vim的配置文件只会影响自己,超级管理员来到你的文件夹里看也不会受到你的vim的影响。
了解了vim的大概使用后,我们就能将用户的权限放大了。
sudo:
用vim打开这个配置文件 /etc/sudoers/
把root这一行yy后面再p,然后wq! 强制保存并退出。
这个配置文件只有root文件才有r的权限,普通用户的权限是000,因此为了写入必须使用!(强制执行),否则会报错。
5. gcc
gcc是一款常见的编译器,下面学习记录他的相关指令。
默认生成的文件叫a.out
gcc和g++的指令书写方法(不唯一,仅是博主个人习惯):
gcc hello.o –o hellogcc -E code.c -o code.igcc -S code.i -o code.sgcc后面跟选项,再跟上希望操作的文件,-o表示从...输入到... , 因此-o后面再接一个目标文件(可以是预先不存在的文件)
除了一次性编译完成,还有多个步骤来编译:
预处理:形成code.i
[lsnm@VM-16-6-centos lesson7]$ vim code.c
[lsnm@VM-16-6-centos lesson7]$ gcc -E code.c -o code.i
[lsnm@VM-16-6-centos lesson7]$ ll
total 84
-rw-rw-r-- 1 lsnm lsnm 71 Nov 7 14:11 code.c
-rw-rw-r-- 1 lsnm lsnm 16870 Nov 7 14:12 code.i
-rw-rw-r-- 1 lsnm lsnm 49 Nov 6 18:28 file.txt
-rw-rw-r-- 1 lsnm lsnm 40651 Nov 7 14:09 test.c
-rwxrwxr-x 1 lsnm lsnm 13064 Nov 6 19:11 test.exe
[lsnm@VM-16-6-centos lesson7]$ vim code.i
打开.i
预处理功能主要包括宏定义 ,文件包含,条件编译,去注释等因此此时依然是C语言代码。
编译:生成code.s
gcc -S code.i -o code.s
这一步在翻译,从高级语言到汇编语言。
汇编:生成code.o
中间的任意一步都可以直接从code.c开始,比如编译时可以:gcc -S code.c -o code.s
但是这一步命令会再进行一次预处理
三个编译指令顺序恰好是ESc,但是使用中更多是一步到位。
6. 重要概念:函数库
写好的程序和 标准库 链接才能变成可执行程序
对可执行程序使用ldd指令:
ldd接一个可执行程序,查看链接了什么库。
可以观察到,两个code都依赖了一个libc(C语言库),我们再打开一个标准指令ls
由此可见,ls也是用C语言写的
7. 动态库与静态库
库的名字叫 libc.so , .so作为后缀的库称之为动态库。与之对应的还有静态库。
不同操作系统中对于两种库的文件后缀。
我们的C程序中,并没有定义“printf”的函数实现,且在预编译中包含的“stdio.h”中也只有该函数的声明,而没有定义函数的实现,那么,是在哪里实“printf”函数的呢?答案是:系统把这些函数实现都放到名为 libc.so.6 的库文件中去了,在没有特别指定时,gcc 会到系统默认的搜索路径“/usr/lib”下进行查找,也就是链接到 libc.so.6 库函数中去,这样就能实现函数“printf”了,而这也就是链接的作用
静态库是指编译链接时 ,把库文件的代码全部加入到可执行文件中,因此生成的文件比较大,但在运行时也就不再需要库文件了。其后缀名一般为“.a” 动态库与之相反,在编译链接时并没有把库文件的代码加入到可执行文件中,而是在程序执行时由运行时链接文件加载库,这样可以节省系统的开销。动态库一般后缀名为“.so”,如前面所述的 libc.so.6 就是动态 库。gcc 在编译时默认使用动态库。
就像一篇论文,如果将参考过的文献的全部都抄进你的论文,读者自然能很方便的查阅,但是整个论文就会过于厚,这就是静态链接;动态链接就是只写一个文献出处,可能A也用过甲文章,B也用过甲文章,大家阅读A和B的时候再去查一个甲文章即可。
这样能极大的减少代码编译的成本等:
必须要有动态库,才能动态链接;必须要有静态库,才能静态链接。
如何查看一个程序是静态链接的还是动态链接的呢?
使用file指令,file + 文件名即可
可以发现一个dynamically linked(动态链接)
如果我们静态链接(使用static选项)一下
gcc -static code -o code_static
就能发现静态链接的文件比动态链接的大了接近80倍
如果不能使用static,请用yum指令安装。云服务器默认没有装C/C++的静态库。
安装好之后就可以观察了:此处的code_static比code大了一百倍。
大量的程序都实现的是动态库。
8. 自动化构建工具:make/Makefile
当我们写好一段代码需要编译的时候,每次都自己用gcc去写太慢了。
现在引入一条指令:make
一个文件夹:makefile或Makefile(m可大写可小写)
我们希望通过make和makefile两个东西,以后直接快捷编译.c文件。
就像在VS中可以直接按F5去调试一样。
语法格式:
目标文件 : 依赖文件
比如我们先用vim创建一个测试文件:
然后touch一个Makefile文件,用vim打开。
格式如下:
首先写依赖关系 目标文件:依赖文件
冒号的左右不要有空格。
然后第二排是依赖方法,依赖方法必须以tab开头,不能用四个空格。
依赖关系是指软件组件间(文件之间)的相互依赖。依赖方法是指管理和解决这些依赖的技术与策略。
将以上内容写在makefile中之后,直接写下指令make就可以编译对应的proc.c到proc了。
make除了会执行命令,本身也会把命令给回显出来
比如我们只想echo一个hahaha,但是会回显一次:
为了不执行回显,需要在指令前加一个“@”
编译成功之后会变成绿色的可执行文件。
.PHONY
phony的英文释义是“假的”
在 Makefile 中,
.PHONY
是一个特殊的目标,用于声明一组目标名称,这些目标不对应于文件系统中的实际文件。.PHONY
的存在是为了告诉make
这些目标是“虚拟”的,即使同名的文件存在,make
也会忽略它们,按照 Makefile 中定义的规则来执行这些目标。
比如现在我已经编译成功了文件,我想要在有一个可以删除编译好的proc.c文件的操作,并且我要给他命名为“clean” clean并不实际存在,所以使用.PHONY
此时的clean没有依赖文件列表,然后写下依赖方法。
make接目标文件名是执行该文件。
clean之后就删除了proc.o和proc
也可以搭配echo做一些显式:
9. make 与 是否执行
会不会写 makefile ,从一个侧面说明了一个人是否具备完成大型工程的能力一个工程中的源文件不计数,其按类型、功能、模块分别放在若干个目录中, makefile 定义了一系列的规则来指定,哪些文件需要先编译,哪些文件需要后编译,哪些文件需要重新编译,甚至于进行更复杂的功能操作
一旦已经形成了可执行,再执行“make”,就会显式已经更新到最新,不会再运行该程序:
但是每次clean都能顺利执行。并且,如果用PHONY修饰了proc,依然能每次都执行编译。
原因如下:
指令:
stat 文件名
三种时间:访问时间、内容修改时间(Mtime)、模式修改时间(Ctime)
只改权限(chmod o-r proc.c),只有属性时间(change)会发生变化
改变内容,属性时间(change)和 内容时间(Modify)都变化。因为改变内容可能会联动属性时间的变化。最近访问时间现在变成一个比较鸡肋的存在,一直和磁盘io交互太麻烦了。
对于源文件和可执行程序:
源文件比可执行程序更新(前面),就会进行一次编译。
可执行程序比源文件更新,就不会进行编译。所以是否执行是根据源文件和可执行程序(也是一种文件)Mtime的前后来判断的。可执行程序在源文件后面,就不会再执行一次编译。而是显式“已经更新到最新”。
而phony
为什么clean不带phony也能执行呢?
因为rm -rf本来就不关心时间,不是所有的指令都关心时间。
在实践运用场景,一般不会全写整个编译过程,但是会写.o来生成一个proc.o
此处的逻辑很像一个栈:
想生成proc,依赖proc.o,于是去找proc.o,然后去找proc.s.......
make解释makefile的时候,是会自动推导的。
一直推导,推导过程,不执行依赖方法。
直到推导到有依赖文件存在,
然后在逆向的执行所有的依赖方法
删除也只需要删除proc和proc.o
%通识符
在Makefile中,
$@
、$^
和$<
是三个常用的自动变量,它们各自有不同的含义和用途:
$@
:这个变量代表当前规则的目标文件(Target)。在多个目标的情况下,$@
会匹配模式规则中定义的目标集合。(冒号左边)
$^
:这个变量代表当前规则中所有依赖文件(Dependencies)的列表,不包括重复的文件。如果依赖列表中有重复的文件,$^
会自动去除重复项,只保留一份。(冒号右边)
$<
:这个变量代表当前规则中第一个依赖文件。如果依赖是模式匹配的,$<
将是符合模式的文件集中的第一个文件。
% 是makefile语法中的通配符。
比如,%c,就表示当前目录下所有的.c文件,展开到依赖列表中。
1 proc:proc.o2 @gcc proc.o -o proc 3 %o:%c4 @gcc -c $<5 @echo "编译完成"6 7 .PHONY:clean8 clean: 9 @rm -rf proc.o proc 10 @echo "已清理"
$<表示将依赖列表中 的依赖文件一个一个传给gcc-c选项,所有的.c都会生成同名的.o
bin和src则可以被理解为“变量”,注意在定义变量时,=左右不能有空格。Makefile中的变量没有类型。
$^ 表示依赖文件中的全部文件
$@代表的是冒号左侧,希望形成的目标文件。
删除的地方也可以改:
bin这种变量的好处是可以用来改名字
或者跳过形成.o的过程,也可以直接形成源文件:
生成两个可执行程序
cat一份pro.c到一个新的code.c中去,现在希望将两个源代码实现两份可执行程序。
尝试一:bin后面多接几个变量
此时能成功生成.o文件,但是不能生成可执行程序,会有“main函数重定义的报错”
因为.o文件对应的就是链接,而此时是两个带有main的.o文件进行链接,就会报错。
尝试二
依然声明一个假指令:all
但是把code和proc分开成两个变量,就不会报错了。
这说明makefile中一次默认只生成一个可执行程序
综上,Makefile有四条性质:
1.makefile文件会被make指令从上到下扫描,第一个目标名,是缺省形成的。如果我们想执行其他组的依赖关系和依赖方法,make 文件名(如上文的proc如果不是第一个,就需要make proc)
2.make在执行gcc命令的时候,如果遇到编译错误,会停止推导过程。
3.make在推导makefile文件的时候,是会自动推导的。如果没有找到依赖文件,暂时不执行依赖方法,而是继续向下推到直到找到目标文件,然后逆向执行所有的依赖方法。
4.make默认只形成一个可执行程序。
10.练习 ,实现Linux中的进度条
回车和换行
\r和\n是不同的,\r表示回车,回车的真正含义是回到该行的起点。\n才是换行,表示光标竖直向下移动。因此,新起一行的本质是\r\n
观察缓冲区:
printf("hello Linux/n")
printf("hello Linux")
加上sleep(2),顺序不一样。最后的结果就不一样。
结论:\n会强制行刷新,而\r不会强制刷新。
程序都需要加载到缓冲区里
首先,肯定都是先执行printf,但是字符串不会马上放到显示器,而是先放到缓存区。\n会自动刷新一次,或者可以使用
fflush(stdout);
以进行手动刷新。
理解一下显示屏的打印,并完成一个简单的倒计时。
结果是10、90、80.......
所以需要fflush来刷新一下
但是count==10的时候就不一样了 ,显示器没有类型的概念,显示器不认识int或者double
只是将数字一个一个的打印出来。10存在之后,回到行首,只打印一个,原来的0还在原处
所以如果想正常从10开始倒计时,需要使用%-2d
没有负号,右对齐
有负号,左对齐
实现进度条:
先创建相关文件,然后用makefile链接三个文件的关系。
版本一:
sleep太长了,换成usleep,再注意下一些简单的格式输出即可。记得使用fflush刷新一下标准输出流,否则是不流畅的。
#define NUM 101 | 5 6 return 0; | 6 #define STYLE '#' | 6 7 | 7 | 7 8 } | 8 void processbar() | 8
~ | 9 { | 9 void processbar();
~ | 10 char lable[4]={'|','/','-','\\'}; |~
~ | 11 char bar[NUM]; |~
~ | 12 memset(bar,'\0',sizeof(bar)); |~
~ | 13 int count = 0; |~
~ | 14 while(count<NUM) |~
~ | 15 { |~
~ | 16 printf("[%-100s][%d%%][%c]\r",bar,count,lable[count%st|~
~ | rlen(lable)]); |~
~ | 17 fflush(stdout); |~
~ | 18 usleep(100000); |~
~ | 19 bar[count]= STYLE; |~
~ | 20 ++count; |~
~ | 21 } |~
~ | 22 printf("\r\n"); |~
~ | 23 }
现在的进度条无法使用,只是简单的从0跑到100
如何和一个程序去联动呢?
先与后文进行一个联动,当前版本的明显不够好用,但是又不想直接删掉这两个版本,于是就把他们改为名字中带backup的文件。
windows中类似于:
现在住程序中模仿一个下载的过程:
每次下载的次数都不一样,所以用随机数生成一个rand(),但是该随机数又要控制在base范围内,所以先设定一个base(也就是下载能力的上线),然后设定一共下载的大小(total)
然后设定获取一次获得的大小(once,每次下载获得的其实就是rand()*once)。当然,建议每下载一次就sleep一段时间,方便打印出对应的进度条。
#pragma once 1 #include "processbar.h" 28 #define STYLE '#'2 #include<stdio.h> | 2 | 29 3 #include<unistd.h> | 3 //模拟下载过程 | 30 extern double total;4 #include<string.h> | 4 double total = 2048.0;//2048MB | 31 void processbar(double current)5 #include<stdlib.h> | 5 double once = 0.1;//0.1MB/time | 32 {6 #include<time.h> | 6 const int base = 100; | 33 //1.更新主进度条7 | 7 | 34 char bar[NUM];8 | 8 void download() | 35 memset(bar,'\0',sizeof(bar));9 void processbar(double current); | 9 { | 36 int count = 0;10 | 10 double current = 0; | 37 for(count =0;count<current/total*100;count++)
~ | 11 while(current<total) | 38 {
~ | 12 { | 39 bar[count]=STYLE;
~ | 13 int r = rand()%base+1;//生成一个[1,base]的随机数 | 40 }
~ | 14 double speed = r*once; | 41 //2.更新数据
~ | 15 current+=speed; | 42 double rate = current/total*100;
~ | 16 if(current>=total){ | 43 //3.更新旋转光标
~ | 17 current = total; | 44 static int pos = 0;
~ | 18 } | 45 char lable[4]={'|','/','-','\\'};
~ | 19 | 46 ++pos;
~ | 20 //display processbar by version2 | 47 pos%=4;
~ | 21 processbar(current); | 48 //打印
~ | 22 //test | 49 printf("[%-100s][%.2lf][%c]\r",bar,rate,lable[pos]);
~ | 23 // printf("%.2lf%% /100.00%%\r",current/total*100); | 50 fflush(stdout);
~ | 24 // fflush(stdout); | 51 }
~ | 25 usleep(10000); |~
~ | 26 } |~
~ | 27 printf("\n"); |~
~ | 28 } |~
~ | 29 |~
~ | 30 int main() |~
~ | 31 { |~
~ | 32 |~
~ | 33 srand(time(NULL)); |~
~ | 34 download(); |~
~ | 35 //processbar(); |~
~ | 36 return 0; |~
~ | 37 |~
~ | 38 }