【命令行魔法:掌握Linux基础工具开发的独门技艺】

本节目标

  • 1.Linux 软件包管理器

  • 2.Linux开发工具

  • 3.Linux编译器-gcc/g++使用

  • 4.Linux项目自动化构建工具-make/Makefile

  • 5.Linux第一个小程序-进度条

1.Linux 软件包管理器

yum 什么是软件包

  • 在Linux下安装软件, 一个通常的办法是下载到程序的源代码, 并进行编译, 得到可执行程序.
  • 但是这样太麻烦了, 于是有些人把一些常用的软件提前编译好, 做成软件包(可以理解成windows上的安装程序)放在一个服务器上, 通过包管理器可以很方便的获取到这个编译好的软件包, 直接进行安装.
  • 软件包和软件包管理器, 就好比 "App" 和 "应用商店" 这样的关系.
  • yum(Yellow dog Updater, Modified)是Linux下非常常用的一种包管理器. 主要应用在Fedora, RedHat, Centos等发行版上.

关于 rzsz

这个工具用于 windows 机器和远端的 Linux 机器通过 XShell 传输文件.

安装完毕之后可以通过拖拽的方式将文件上传过去.

注意事项

关于 yum 的所有操作必须保证主机(虚拟机)网络畅通!!!

可以通过 ping 指令验证

ping www.baidu.com

查看软件包

通过 yum list 命令可以罗列出当前一共有哪些软件包. 由于包的数目可能非常之多, 这里我们需要使用 grep 命令只 筛选出我们关注的包. 例如:

yum list | grep lrzsz

结果如下:

注意事项:

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

如何安装软件

通过 yum, 我们可以通过很简单的一条命令完成 gcc 的安装.

sudo yum install lrzsz

yum 会自动找到都有哪些软件包需要下载, 这时候敲 "y" 确认安装. 如果上面指令加上-y选项,此时不需要敲 "y" 确认安装. 出现 "complete" 字样, 说明安装完成.

注意事项:

  • 安装软件时由于需要向系统目录中写入内容, 一般需要 sudo 或者切到 root 账户下才能完成.
  • yum安装软件只能一个装完了再装另一个. 正在yum安装一个软件的过程中, 如果再尝试用yum安装另外 一个软件, yum会报错.
  • 如果 yum 报错, 请自行百度.

安装几个有意思的指令

yum install -y sl

如何卸载软件

仍然是一条命令:

sudo yum remove lrzsz

问题:yum如何知道目标服务器的地址和下载链接呢?比如我们在手机的应用商店上下载一个抖音,应用商店是怎么知道目标服务器的地址呢?它怎么知道要去哪里下载呢?

        当用户决定下载应用时,应用商店会生成一个下载请求,将该请求发送到应用商店的服务器。应用商店的服务器会根据下载请求检索应用的信息,包括下载链接。同样的LInux下yum也是这样,yum会生成一个下载请求(请求路径由配置文件提供),将该请求发送到应yum的远端的指令仓库,这个指令仓库会根据下载请求检索应用的信息,包括下载链接。

liunx通过配置文件的提供的地址去寻找yum的远端的指令仓库,查看配置文件:ls /etc/yum.repos.d/,CentOs-Base.repo就是Linux下的配置文件。

指令仓库中包含了各个软件的下载链接。

如果我们访问的yum源是国外的,我们访问起来速度会有点慢,因此我们可以更替换我们的Linux下的配置文件CentOs-Base.repo,更新我们的yum源。

1.更新yum源之前,需要备份当前的yum源,以便出现问题时可以恢复,可以通过以下命令备份:

cp /etc/yum.repos.d/Centos-Base.repo /etc/yum.repos.d/Centos-Base.repo.backup

2.下载新的yum源文件:这里下载的时阿里云镜像站点的Centos 7 yum源文件

wget -O /etc/yum.repos.d/Centos-Base.repo http://mirrors.aliyun.com/repo/Centos-7.repo

此时我们打开保存的yum源:vim Centos-7.repo,此时就更新了我们的yum源

3.清除yum缓存:更新yum源后,需要清除yum缓存,以便系统能够识别新的yum源文件

yum clean all

4.更新yum缓存:清除yum缓存后,需要更新yum源后,以便系统能够识别新的yum源文件中的软件包信息

yum makecache

5.测试新的yum源

yum list

我们该如何使用这个yum源呢?我们需要将这个yum源更改成我们的配置文件

mv Centos-7.repo CentOS-Base.repo

然后我们再以root身份执行:yum install -y epel-release,看看 cd /etc/yum.repo/有没有epel.repo文件

上面的CentOS-Base.repo是基础软件源,内部软件都比较成熟稳定,epel.repo是扩展软件源是新增的软件,待新增的软件稳定后移入到新增的软件基础软件源中,基础软件源再把一些淘汰的软件删掉,这样就能保持基础软件源的更新,同时新增的不稳定软件不放人基础软件源,保护基础软件源的安全。所以以后下载软件可以选择epel.repo。

2.Linux开发工具

Linux编辑器-vim使用

vi/vim的区别简单点来说,它们都是多模式编辑器,不同的是vim是vi的升级版本,它不仅兼容vi的所有指令,而且还有一些新的特性在里面。例如语法加亮,可视化操作不仅可以在终端运行,也可以运行于x window、 mac os、 windows。统一按照vim来进行讲解。

1. vim的基本概念与模式

vim是一个多模式的编辑器,vim里面还有很多的字命令,来进行代码的编写操作。vim的三种模式(其实有很多模式,目前掌握这3种即可),分别是命令模式(command mode)插入模式(Insert mode)底行模式(last line mode),各模式的功能区分如下:

  • 正常/普通/命令模式(Normal mode) - vim默认打开的方式

控制屏幕光标的移动,字符、字或行的删除,移动复制某区段及进入Insert mode下,或者到 last line mode。此模式下的输入都当作命令来看待,除非误触了模式切换的命令。

  • 插入模式(Insert mode)

只有在Insert mode下,才可以做文字输入,按「ESC」键可回到命令行模式。该模式是我们后面用的最频繁的编辑模式。

  • 末行模式(last line mode)

文件保存或退出,也可以进行文件替换,找字符串,列出行号等操作。 在命令模式下,shift + :(等价于: )即可进入该模式。

要查看你的所有模式:打开vim,底行模式直接输入

:help vim-modes

这里一共有12种模式:six BASIC modes和six ADDITIONAL modes.

2. vim的基本操作以及模式切换

进入vim,在系统提示符号输入vim及文件名称后,就进入vim全屏幕编辑画面:

  • $ vim test.c
  • 不过有一点要特别注意,就是你进入vim之后,是处于[正常模式],你要切换到[插入模式]才能够输入文字。

[命令模式]切换至[插入模式]

  • 输入a:按下小写字母 "a" 键,这会将光标移到当前位置的下一个字符处,并进入插入模式。
  • 输入i:按下小写字母 "i" 键,这会将光标放置在当前位置并进入插入模式
  • 输入o:按下小写字母 "o" 键,这会在当前行的下方插入一个新行,并进入插入模式。

[插入模式]切换至[命令模式]

  • 目前处于[插入模式],就只能一直输入文字,如果发现输错了字,想用光标键往回移动,将该字删除,可以先按一下「ESC」键转到[正常模式]再删除文字。当然,也可以直接删除。

[命令模式]切换至[末行模式]

  • 「shift + ;」, 其实就是输入「:」

[末行模式]切换至[命令模式]

  • 按一下「ESC」键转到[正常模式]

退出vim及保存文件,在[命令模式]下,按一下「:」冒号键进入「Last line mode」,例如:

  • : w (保存当前文件)
  • : wq (输入「wq」,存盘并退出vim)
  • : q! (输入q!,不存盘强制退出vim)

3. vim正常模式命令集操作

插入模式

我们先来将我们的光标设置一下,将其设置再#的位置处。

  • 按「i」切换进入插入模式「insert mode」,按“i”进入插入模式后是从光标当前位置开始输入文件;

  • 按「a」进入插入模式后,是从目前光标所在位置的下一个位置开始输入文字;

  • 按「o」进入插入模式后,是插入新的一行,从行首开始输入文字。

从插入模式切换为命令模式

  • 按「ESC」键。

移动光标

  • vim可以直接用键盘上的光标来上下左右移动,但正规的vim是用小写英文字母「h:左」「j:下」「k:上」「l:右」,分别控制光标左、下、上、右移一格

  • 按「G」:移动到整个文本的最后

  • 按[gg]:进入到整个文本的开始

  • 按[n + G]:光标进入第n行行首,如:第5行

  • 按「 $ 」:移动到光标所在行的“行尾”

  • 按「^」:移动到光标所在行的“行首”

  • 按「w」:光标跳到下个字(单词)的开头

  • 按「e」:光标跳到下个字(单词)的字尾

  • 按「b」:光标回到上个字(单词)的开头

  • 按「#l」:光标移到该行的第#个位置,如:5l,56l
  • 按「ctrl」+「b」:屏幕往“后”移动一页
  • 按「ctrl」+「f」:屏幕往“前”移动一页
  • 按「ctrl」+「u」:屏幕往“后”移动半页
  • 按「ctrl」+「d」:屏幕往“前”移动半页

删除文字

  • 「x」:每按一次,删除光标所在位置的一个字符,光标位置不会被保留

  • 「#x」:例如,「6x」表示删除光标所在位置的“后面(包含自己在内)”6个字符
  • 「X」:大写的X,每按一次,删除光标所在位置的“前面”一个字符,光标位置会被保留

  • 「#X」:例如,「20X」表示删除光标所在位置的“前面”20个字符
  • 「dd」:删除光标所在行,如果与“p”配合就是剪切与粘贴的功能

  • 「dd + p」:剪切光标所在行,让后在光标所在处进行粘贴

  • 「#dd」:从光标所在行开始删除#行

复制

  • 「yw」:将光标所在之处到字尾的字符复制到缓冲区中。
  • 「#yw」:复制#个字到缓冲区
  • 「yy」:复制光标所在行到缓冲区。
  • 「#yy」:例如,「6yy」表示拷贝从光标所在的该行“往下数”6行文字。
  • 「#p」:将缓冲区内的字符贴到光标所在位置。注意:所有与“y”有关的复制命令都必须与“p”配合才能完成复制与粘贴功能。
  • 「yy + p」:复制当前行并在当前行的下一行进行粘贴。

  • 「n + yy + p」表示拷贝yy复制光标所在行的6行文字,并在下一行粘贴。

大小写快速切换

  • 「shift + `」:大小写快速切换

替换

  • 「r」:替换光标所在处的字符。

​​​​​​​

  • 「shifit + r = R」:切换到替换模式,替换光标所到之处的字符,直到按下「ESC」键为止。

撤销上一次操作

  • 「u」:如果您误执行一个命令,可以马上按下

  • 「u」,回到上一个操作。按多次“u”可以执行多次恢复。
  • 「ctrl + r」: 撤销的恢复

更改

  • 「cw」:更改光标所在处的字到字尾处
  • 「c#w」:例如,「c3w」表示更改3个字

跳至指定的行

  • 「ctrl」+「g」列出光标所在行的行号。
  • 「#G」:例如,「15G」,表示移动光标至文章的第15行行首。
  • 「shift + 3 = #」:高亮要查找的多次使用的函数名(批量选中),「n」:下一个要查找的函数名

4. vim末行模式命令集操作

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

列出行号

  • 「set nu」: 输入「set nu」后,会在文件中的每一行前面列出行号。

  • 「set nonu」: 输入「set nonu」后,会取消文件中的每一行前面的行号。
  • 「vim test.c +5」:让vim在打开的时候光标设置到第5行

跳到文件中的某一行

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

查找字符

  • 「/关键字」: 先按「/」键,再输入您想寻找的字符,如果第一次找的关键字不是您想要的,可以一直按 「n」会往后寻找到您要的关键字为止。
  • 「?关键字」:先按「?」键,再输入您想寻找的字符,如果第一次找的关键字不是您想要的,可以一直 按「n」会往前寻找到您要的关键字为止。
  • 问题:/ 和 ?查找有和区别?操作实验一下

保存文件

  • 「w」: 在冒号输入字母「w」就可以将文件保存起来,如果无法写入,可以在「w」后跟一个「!」强制写入。
  • 「!command」:指令执行完后,输入q,再输入enter即可再次进入vim

离开vim

  • 「q」:按「q」就是退出,如果无法离开vim,可以在「q」后跟一个「!」强制离开vim。
  • 「wq」/「shift zz = ZZ(在命令模式下使用)」 :一般建议离开时,搭配「w」一起使用,这样在退出的时候还可以保存文件。
  • 「ctrl + ww」:多窗口光标切换
  • 「vs filename」:对比文件光标在那一个界面,我们就正在编辑哪一个界面,底行也是这样。

  • 「vim filename + n」:启动vim设置光标的位置在第n行 
  • 「ctrl + v」:切换为视图模式,按下「j」选择区域,再输入「shift + i = I」切换为插入模式,然后在输入「//」,按入「esc」即可批量化注释

  • 「ctrl + v」:切换为视图模式,按下「j」选择区域,在输入d,即可批量化取消注释。

5. 简单vim配置

配置文件的位置

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

常用配置选项,用来测试

  • 设置语法高亮: syntax on
  • 显示行号: set nu
  • 设置自动缩进:set autoindent 和 set cindent
  • 设置统一缩进为4:set softtabstop=4 和 set shiftwidth=4
inoremap ( ()<ESC>i  "设置(自动补全
inoremap [ []<ESC>i  "设置[自动补全
inoremap { {}<ESC>i  "设置{自动补全
inoremap < <><ESC>i  "设置<自动补全
inoremap ' ''<ESC>i  "设置'自动补全
inoremap " ""<ESC>i  "设置"自动补全
set nu               "设置显示行号
set tabstop=2        "设置tab健的长度为2
set ruler            "设置标尺
set ai               "设置文本高亮
set autoindent       "设置自动缩进(与上一行的缩进相同)

使用插件

要配置好看的vim,原生的配置可能功能不全,可以选择安装插件来完善配置,保证用户是你要配置的用户,接下来:

在 shell 中执行指令(想在哪个用户下让vim配置生效, 就在哪个用户下执行这个指令. 强烈 "不推荐" 直接在 root 下执行):curl -sLf https://gitee.com/HGtz2222/VimForCpp/raw/master/install.sh -o ./install.sh && bash ./install.sh,需要按照提示输入 root 密码. 您的 root 密码不会被上传, 请放心输入.

然后在bash命令行窗口输入:source ~/.bashrc就可以使用配置好的vim了。

参考资料

Vim从入门到牛逼(vim from zero to hero)

历史问题:当我们用sudo进行指令提权为什么还是不能执行?

我们可以想一下,我们上面对指令进行sudo提权,是输入自己的密码,输入自己的密码就可以执行root的指令,这样就会有很大的风险。所以我们在创建用户的时候默认在./etc/sudoers/下没有添加到白名单中,我们可以先切换root的身份,并进入root的家目录下

然后我们vim打开./etc/sudoers/文件,我们发现文件时空白的,并且文件不可被允许操作

这是因为此时我们的文件只对root是可读的,对于其他用户是没有任何权限的,并且root用户也只有对该文件的可读,所以我们上面才要切换为root账号。

在光标处下添加当前用户

随后我们进行wq保存并退出,发现并不行,这是因为我们的root对于这个文件也只有读权限,并没有写权限。

所以我们就可以通过w!进行强制写入和q!强制退出就可以了,然后我们再执行sudo进行指令提权

3.Linux编译器-gcc/g++使用

C语言使用gcc(推荐)/g++编译,C++只能使用g++编译。

1. 背景知识

  • 1. 预处理(进行宏替换)
  • 2. 编译(生成汇编)
  • 3. 汇编(生成机器可识别代码)
  • 4. 连接(生成可执行文件或库文件)

2. gcc如何完成

格式 gcc [选项] 要编译的文件 [选项] [目标文件]

我们先来写一段代码,方便后续的观察

预处理(进行宏替换)
  • 预处理功能主要包括头文件展开,宏替换,条件编译,去注释等。
  • 所谓的头文件展开,本质就是在预处理的时候,将头文件的内容拷贝至源文件。
  • 预处理指令是以#号开头的代码行。
  • 对于条件编译,可通过给编译器传递不同的宏值,来进行对代码的的动态裁剪!
  • 实例:gcc code.c -o code.exe  -D0 VERSION=1
  • 实例: gcc –E code.c –o code.i
  • 选项“-E”,该选项的作用是让 gcc 在预处理结束后停止编译过程,并把预处理的结果打印在
  • 选项“-o”是指目标文件,“.i”文件为已经过预处理的C原始程序。
编译(生成汇编)-  计算机只认识二进制 - C语言->汇编语言
  • 在这个阶段中,gcc 首先要检查代码的规范性、是否有语法错误等,以确定代码的实际要做的工作,在检查 无误后,gcc 把代码翻译成汇编语言。
  • 用户可以使用“-S”选项来进行查看,该选项只进行编译而不进行汇编,生成汇编代码。
  • 实例: gcc –S code.i –o code.s
汇编(生成机器可识别代码)汇编语言->可重定位二进制文件:不能被执行的

  • 汇编阶段是把编译阶段生成的“.s”文件转成目标文件
  • 读者在此可使用选项“-c”就可看到汇编代码已转化为“.o”的二进制目标代码了

  • 实例: gcc –c code.s –o code.o
链接(生成可执行文件或库文件)
  • 在成功编译之后,就进入了链接阶段。
  • 实例: gcc/g++ code.o –o code 或者 gcc/g++ -o code code.o
在这里涉及到一个重要的概念:函数库
  • 一个平台要支持开发,就必须要提前在系统中安装语言的标准头文件+库文件
  • 我们的C程序中,并没有定义“printf”的函数实现,且在预编译中包含的“stdio.h”中也只有该函数的声明,而 没有定义函数的实现,那么,是在哪里实“printf”函数的呢?
  • 最后的答案是:系统把这些函数实现都被做到名为 libc.so.6 的库文件中去了,在没有特别指定时,gcc 会到 系统默认的搜索路径“/usr/lib”下进行查找,也就是链接到 libc.so.6 库函数中去,这样就能实现函 数“printf”了,而这也就是链接的作用

问题:拿汇编语言举例,先有的汇编编程语言还是先有的汇编程编译器

这个问题似乎和先有蛋还是现有鸡的问题一样,其实不然,早期我们使用二进制写程序的,由于二进制写起来比较麻烦,所以出现了汇编语言,那么此时汇编语言由谁编译呢?肯定是汇编语言编译器,但是这个汇编语言编译器不会是用汇编语言写的,所以汇编语言编译器是用二进制去写的,汇编语言使用二进制编写的编译器去编译的,从而最后形成软件。编译器也是软件,后续就有人用汇编语言写了一个汇编语言编译器通过二进制编写的编译器去编译,最后才出现了我们的汇编语言编译器。对于先有的汇编编程语言还是先有的编程编译器,我们可以确定的是一定先有二进制编写的编译器。

函数库一般分为静态库和动态库两种。

  • 静态库是指编译链接时,把库文件的代码全部加入到可执行文件中,因此生成的文件比较大,但在运行时也 就不再需要库文件了。其后缀名一般为“.a”
  • 动态库与之相反,在编译链接时并没有把库文件的代码加入到可执行文件中,而是在程序执行时由运行时 链接文件加载库,这样可以节省系统的开销。动态库一般后缀名为“.so”,如前面所述的 libc.so.6 就是动态库。gcc 在编译时默认使用动态库。完成了链接之后,gcc 就可以生成可执行文件,如下所示。
  • gcc hello.o –o hello gcc默认生成的二进制程序,是动态链接的,这点可以通过 file 命令验证。
  • 动静态库都是文件,头文件也是文件。

动态库的优点:所有人都去网咖共享一台电脑,比较节省资源,不会出现太多的重复代码 --- 节省的是磁盘空间,内存和网络等资源。缺点:一旦网咖因为运营而关闭,所有人都不能使用了,对库的依赖性比较强,一旦库丢失,所有使用这个库的程序都无法运行。

静态库的优点:不依赖库,同类型平台中都可以直接使用。缺点:可执行程序体积比较大,比较浪费资源 --- 磁盘空间,内存和网络等资源。

gcc默认生成的二进制程序,是动态链接的,这点可以通过 file 命令验证。ldd可以查询一个可执行程序所依赖的库文件。

我们也可以通过指令对可执行程序进行静态链接:gcc -o mybin-static mytest.c -static

这里我们也可以发现静态链接形成的可执行程序大小非常大,静态链接是把库中得代码拷贝过来。

但是我们的云服务器默认是没有安装静态库的,所以我们执行:gcc -o mybin-static mytest.c -static 是会报错的

上面报错信息中ld是我们的链接器,-l是gcc的另外一个选项,c就是说明我们缺少一个c标准库,我们库的名称是去掉前缀lib和后缀.so.6。所以我们是需要安装我们的静态库的,执行指令:

sudo yum install -y glibc-static libstdc++-static
安装完我们可以直接去/lib64/libc.a路径下去查看我们安装的静态库
gcc选项
  • -E 只激活预处理,这个不生成文件,你需要把它重定向到一个输出文件里面
  • -S  编译到汇编语言不进行汇编和链接
  • -c  编译到目标代码
  • -o 文件输出到 文件
  • -static 此选项对生成的文件采用静态链接
  • -g 生成调试信息。GNU 调试器可利用该信息。
  • -shared 此选项将尽量使用动态库,所以生成文件比较小,但是需要系统由动态库.
  • -O0 -O1 -O2 -O3 编译器的优化选项的4个级别,-O0表示没有优化,-O1为缺省值,-O3优化级别最高
  • -w  不生成任何警告信息。
  • -Wall 生成所有警告信息。

4.Linux项目自动化构建工具-make/Makefile

4.1背景

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

我们先来创建一个makefile文件,然后vim打开makefile文件,输出内容:

然后我们在输入make指令就形成了可执行程序mybin。

4.2依赖关系和依赖方法

这里通过一个小故事了解一下它们之间的关系:

        月末了你已经没有生活费了,此时的你就会向你老爸要生活费,然后你向你老爸说了一句我是你的儿子,然后又说月末了我没有生活费了。对老爸说我是你的儿子这句话就是表面依赖关系,解释的是为什么我要帮你,月末了我没有生活费这句话就是具体的依赖方法,解释的是如何帮你。

如果我们再次输入make呢?

此时显示该文件不需要再编译,当前的mybin已经是最新文件了,我们并没有对源文件test.c进行过任何修改,所以源文件没有变化,我们的可执行程序再编译的结果是一样的,所以gcc也就不再编译了。如果我们改一下源文件,那么make会让我们再编译嘛?答案是可以滴。

这里提一个问题,make和makefile怎么知道当前文件时最新的呢?首先我们要清楚,如果这个源文件我们一直没有修改,就算过了一万年,他仍然是最新的,仍然是不可编译的,所以这个肯定是与我们现在所处的时间概念无关的。但是它肯定与时间有关,但是这个时间是对比出来的,常识告诉我们,改一个源文件就需要打开该源文件,修改之后该文件的修改时间就会被改变,只要可执行程序的最近修改时间比所有源文件最近的修改时间新,说明它就是最新的!

我们可以通过stat+filename查看文件的时间信息

当我们再次vim打开该文件进行修改,再次执行stat+filename

此时我们发现文件的Modify时间就发生了变化,然后我们再make指令编译一下源文件,再次执行stat+filename

我们可以发现mybin的时间永远是源文件test.c更新。通过stat我们可以查看到Modify时间,那么Change时间是什么呢?我们之前提到文件=内容+属性。Modify是对于文件内容而言的,而Change是对于文件属性来说的。通过ll指令查看的就是文件的属性:权限,拥有者和所属组等等。

通过给other去掉读权限,我们可以看到文件的Change时间发生变化

随后我们再对文件内容进行修改,此时会发生什么呢?

我们会发现此时的Modify时间改变了,但是Change时间也发生了改变,因为我们对源文件修改的时候,可能增加或者删除了代码,此时我们文件的大小就可能发生变化,所以Change时间也发生了改变。我们再来了解一下Access时间,它是文件的访问时间。

然后我们多次执行访问文件,我们会发现Access时间没有改变,这是因为Access不是实时更新的,而是访问到一定的次数或者是在系统内部做了一定的访问操作,当系统认为可以时间可以更新便会自动更新。

结论:关于对比可执行程序和我们的源文件哪一个更新的时候,我们通常以Modify时间为准。

如果我们想在不改变源文件内容的时候,我们可以通过修改文件的Modify时间去让make能够重新编译。我们可以使用touch命令,如果文件不存在,touch帮我们创建文件,如果文件存在,touch就会帮我们更新文件。

我们还可以打开vim mytest.c,然后将它设置为伪目标,用 .PHONY 修饰,伪目标的特性是,总是被执行的。

此后我们再输入make编译,此时就不再显示文件是最新的而不可被编译

不过我们一般不将我们的目标文件设置成伪文件,而是将clean设置成伪文件,因为清理工作总是要执行的。

我们也可以将上面的makefile这样写:@表示目标文件,^表示源文件列表。

或者也可以这样写,makefile是支持变量定义的,我们可以将它理解为宏,完成的任务是替换。

这里我们还需要了解一个问题mybin是直接依赖我们的test.c吗?根据我们上面提到滴,并不是,我们写的代码test.c需要经过预处理、编译、汇编和链接才可以形成可执行程序mybin。

  • 上面的文件 mybin ,它依赖 test.o
  • test.o , 它依赖 test.s
  • test.s , 它依赖 test.i
  • test.i , 它依赖 test.c

上面的这个依赖关系和依赖方法是类似于栈的,依赖关系先依次入栈,然后再出栈于相应的依赖方法相匹配。

4.3原理                                                 

  • make是如何工作的,在默认的方式下,也就是我们只输入make命令。那么

1. make会在当前目录下找名字叫“Makefile”或“makefile”的文件。

2. 如果找到,它会找文件中的第一个目标文件(target),在上面的例子中,他会找到“mybin”这个文件, 并把这个文件作为最终的目标文件。

3. 如果mybin文件不存在,或是mybin所依赖的后面的test.o文件的文件修改时间要比mybin这个文件新(可以用 touch 测试),那么,他就会执行后面所定义的命令来生成mybin这个文件。

4. 如果mybin所依赖的test.o文件不存在,那么make会在当前文件中找目标为test.o文件的依赖性,如果找到则再根据那一个规则生成test.o文件。(这有点像一个堆栈的过程)

5. 当然,你的C文件和H文件是存在的啦,于是make会生成 test.o 文件,然后再用 test.o 文件声明 make的终极任务,也就是执行文件mybin了。

6. 这就是整个make的依赖性,make会一层又一层地去找文件的依赖关系,直到最终编译出第一个目标文件。

7. 在找寻的过程中,如果出现错误,比如最后被依赖的文件找不到,那么make就会直接退出,并报错, 而对于所定义的命令的错误,或是编译不成功,make根本不理。

8. make只管文件的依赖性,即,如果在我找了依赖关系之后,冒号后面的文件还是不在,那么对不起, 我就不工作啦。

4.4项目清理

  • 工程是需要被清理的
  • 像clean这种,没有被第一个目标文件直接或间接关联,那么它后面所定义的命令将不会被自动执行, 不过,我们可以显示要make执行。即命令——“make clean”,以此来清除所有的目标文件,以便重编译。
  • 但是一般我们这种clean的目标文件,我们将它设置为伪目标,用 .PHONY 修饰,伪目标的特性是,总是被执行的。
  • 可以将我们的 hello 目标文件声明成伪目标,测试一下。

怎么清理呢?vim打开Makefile        

即命令——“make clean”,以此来清除所有的目标文件,以便重编译。

我们再来改一下vim里面的顺序

1.Makefile和make形成目标文件的时候,默认是从上到下扫描makefile文件的,默认形成的是第一个目标文件。 

2.有多个依赖关系和依赖方法,默认只形成一个。

5.Linux第一个小程序-进度条

5.1.缓冲区概念

首先我们将makefile和源文件搭建好

然后我们验证一下我们写的是否有问题

然后我们验证两个代码,观看它们的现象。

#include <stdio.h>
int main()
{
    printf("hello Makefile!\n");
    sleep(3);
    return 0;
}

我们可以发现上面代码的运行结果是先输出printf,然后等待三秒

#include <stdio.h>
int main()
{
    printf("hello Makefile!");
    sleep(3);
    return 0;
}

我们发现上代码的运行结果是先等待三秒,然后再输出printf

但是我们不要被上面的现象所迷惑,C语言代码是遵守顺序结构的,所以我们可以知道上面的代码永远是先执行printf("hello Makefile!");然后在执行sleep(3);但是为什么会出现上面的想象呢?

这是因为我们的C语言有一个缓冲区,当sleep没有执行完的时候,printf的字符串被保存到缓冲区了,当sleep执行完的时候,此时就要求刷新缓冲区,于是此时才输出printf语句,而第一个代码直接输出是因为'\n'字符会立刻刷新缓冲区,于是就先输出了printf语句。

#include <stdio.h>
int main()
{
    printf("hello Makefile!");
    fflush(stdout);
    sleep(3);
    return 0;
}

我们可以通过手动刷新缓冲区,先输出printf内容。

5.2.\n:换行符与\r:回车符之间的区别

所以准备工作已经完成了,现在开始写我们的进度条小程序。

5.3.进度条代码

但是此时我们发现make编译后运行我们的程序没有任何结果

因为我们上面的printf内容被保存到缓存区了,所以没有输出我们想要的结果,所以我们就要使用fflush进行强制刷新缓冲区。

然后我们再运行我们的代码。

这样就实现了我们的倒计时小程序,不过我们上面只能从9倒计时,如果我们想从10倒计时呢?

我们发现我们的程序不能完成从10倒计时,输出的时候后面多了一个0,为什么呢?

当我们向显示器打印10的时候,打印的是字符'1'和'0',当我们后面打印的时候,字符只依次覆盖'1'字符,并没有覆盖'0'字符。怎么解决呢?-2d:表示输出这个整数的时候预留两个字符的空间并左对齐输出这个整数

输出结果:

现在我们就来写一下我们的进度体条小程序,首先学习一下usleep函数,按照微秒的时间进行休眠。

我们先来建立一下多个文件并写上一个简单的打印输出工作

然后我们先验证一下make能否编译

接下来就可以写我们的进度条了。

我们的程序输出的进度条主要在processbar.c中实现的,字符数组设置为102是因为在我们的程序中,循环共循环了101次,bar[100]='#',如果设置数组大小101,此时下标最大就是100,而100下标处存放了bar[100]='#',没有位置存放'\0',这样程字符串就没有结束的标识,程序就达不到预期的结果。这里要注意最后一次,下标的位置是bar[100]='#',那从0开始到100不就有101个'#'符号吧吗?并不是,我们自己看我们的程序,当下标为0的时候,我们的程序时不打印'#'的,下标为1的时候才打印'#'的。

#include <iostream>
using namespace std;
int main()
{
	int cnt = 0;
	char bar[5];
	memset(bar, '\0', sizeof(bar));
	while (cnt <= 3)//下标为0,1,2,3
	{
		printf("[%s]", bar);
		bar[cnt++] = '#';
	}
	return 0;
}

运行结果:

所以此时小程序下标就是100,即可输出100个字符'#",结果运行:

但是我们平常的进度条不是#,我们期待能出现一个箭头的形式,我们可以在0下标处设置为字符'<',后续用字符'='覆盖前面的字符'<',我们来看一下程序的运行结果:

我们发现上面的程序输出了101个字符,我们期待最后一个字符应该是'=',我们来看一下我们的程序,当i=100的时候,此时bar[100]='>',循环再次进入的时候,最后一个元素输出是'>',然后i++,bar[100]位置被修改为'=',然后i++变为101,此时不再进入循环,虽然bar[100]位置被修改为'=',但是此时没有执行打印输出工作,所以最后一个仍然是'>'字符。

#include <iostream>
using namespace std;
int main()
{
	int cnt = 0;
	char bar[6];
	memset(bar, '\0', sizeof(bar));
	bar[0] = '>';
	while (cnt <= 3)//下标是0,1,2,3
	{
		printf("[%s]", bar);
		bar[cnt++] = '=';
		if (cnt <= 3)
			bar[cnt] = '>';
	}
	return 0;
}

运行结果:

监视窗口可以看到bar最后一个元素是'=',只不过输出的时候没有打印出来。

因此我们可以修改一下我们的程序,当i<100的时候就不要再执行修改'>'了。

但是实际上进度条并不是一个独立的程序,而是依附于其他应用的,比如我们下载一个文件就要进度条,进度条应该表现为下载量除以文件大小。接下来我们模拟一个下载的场景。

运行结果:

因此我们可以改善一下进度条。

上面的程序我们使用了我们的函数指针,通过回调函数可以调用函数,并且我们修改旋转光标不和rate进度条相关,并且给printf输出加上颜色打印,使我们的进度条更加美观。

进度条:

本章结束啦!!!

  • 61
    点赞
  • 60
    收藏
    觉得还不错? 一键收藏
  • 58
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值