🔥个人专栏:Linux—登神长阶
⛺️ 欢迎关注:👍点赞 👂🏽留言 😍收藏 💞 💞 💞
🚀前言
💢在实现进度条之前,我们先来了解一下换行和回车,以及缓冲区的概念,以便于我们来实现进度条,注:我们还需要用到上篇文章的知识:【Linux必备工具】自动化构建工具makefile的使用详解-CSDN博客
1. 回车与换行
我们在学C语言的时候,发现当我们在一行内容没有写完,然后要换到下一行的开始,我们要进行两个操作,
1:\n(换行)
让光标从第一行跳到第二行,但是光标只是垂直向下跳,并没有在第二行的开始。
2:\r(回车)
在第二行让光标跳到最开始的位置,这个操作就是回车。
那为啥我们在C语言的时候,怎么用\n来换行加回车?
因为这是我们在这个语言环境下我们将其简化,此时的\n就表示回车加换行。
测试字符
我们一共测试四种情况,
- \r和\n都存在
- \r和\n都不存在
- 只有\n
- 只有\r
🍎(1) \r和\n都存在
看下图可发现,我们在其中既有回车又有换行,所以其
linux
的命令行在我们执行程序下面。
🍌(2) \r和\n都不存在
看下图可发现:Linux命令行紧跟着打印的信息,
因为我们结尾啥都没有,那光标就还在结尾,所以Linux命令行紧跟我们的打印信息
🍐(3) 只有\n
这里我们就省略演示了,毕竟这个现象和1相同,就是和我们的换行+回车,和我们平时用的一样,只不过我们的编译器将两步简化为一步了,我们只需要输入\n即可
🍇(4) 只有\r
现象:看不到打印的信息了。只能看到linux命令行
💢解释:因为我们\r
,只有回车的效果,我们本来光标在文本行的最后一个字符旁边,但是我们识别到\r字符,所以,直接回车,光标来到了文本行的开始。这时linux
的命令行就会从光标处开始,将我们的文本覆盖掉,我们就什们都看不到。
2. 缓冲区
2.1 缓冲区概念
💢缓冲区是计算机内存的一部分,用于暂时存储数据。它在数据传输过程中起到一个缓冲桥梁的作用,帮助协调数据传输的速度差异。缓冲区可以是磁盘缓存,网络传输中的数据缓存等。
2.2 缓冲区作用
缓冲区的作用非常广泛和重要,主要体现在以下几个方面:
提升读写效率
- 当进程要进行文件读写操作时,数据会首先存储在缓冲区中,而不是直接写入磁盘。缓冲区根据特定的刷新策略定期或在特定条件下将数据写入磁盘。这样可以减少磁盘的频繁读写动作,从而提升整体系统的效率。
减少等待时间
- 在没有缓冲区的情况下,每次文件读写操作都需要等待外设(如磁盘)就绪,这可能会导致显著的等待时间。缓冲区减少了这种等待时间,因为数据可以暂时存储在内存中,进程可以继续进行其他任务,而无需等待外设操作完成。
我们先来分析下面几段代码感受一下行缓冲区的存在:
在Linux当中以下代码的运行结果是什么样的?
#include <stdio.h>
#include <unistd.h>
int main()
{
printf("你能看见我嘛\n");
sleep(2);
return 0;
}
这段代码运行结果显而易见:printf
函数直接先打印内容,然后再休眠2秒。
注:sleep的作用是按秒去休眠,头文件为<unisted.h>
然后当我们去除后面的 '\n' 则会出现什么了
缓存
💢实际上,printf 已经先执行了,只是这个 "Helo,World" 没有立马被显示出来罢了!
当我们 sleep 时也没有显示,当我们 sleep 完甚至到程序退出后,这个 "Helo,World" 才显示出来。
💢说明带有\n会马上刷新,那么不带的时候,那2s 代码就是存在我们的缓冲区, return 退出的时候,这个数据才显示出来,所以才看到了我们现在看到的现象 。
2.3 缓冲区刷新策略
缓冲区的刷新策略决定了何时将缓冲区中的数据真正写入到目标存储器,如磁盘或显示器。主要有以下几种策略:
a、无缓冲(Unbuffered)
数据一写入缓冲区就立即刷新写入目标设备。这种方式适合对时间敏感的操作,但可能导致系统资源的低效利用。
b、行缓冲(Line Buffered)
当缓冲区检测到换行符(\n)时,立即刷新写入目标设备。这种方式常用于终端显示器,以保证一行行的输出效果。例如,在终端或控制台输出时,行缓冲能确保即时显示用户输入的一行内容。
c、全缓冲(Fully Buffered)
只有当缓冲区满了时,才会将数据刷新写入目标设备。这种方式适合大量数据的写入操作,能提高整体的写入效率。例如,在将数据写入磁盘文件时,通常使用全缓冲策略。
d、特殊策略
- 用户强制刷新
用户可以显式调用刷新函数(如fflush(FILE *stream)
)来强制刷新缓冲区内容。- 进程退出刷新
当进程正常退出时,缓冲区会自动刷新,以确保所有已写入缓冲区但尚未写入目标设备的数据都被处理完毕。
如果我们想让上面的hello马上打印,就可以进行如下操作啦
#include <stdio.h>
#include <unistd.h>
int main()
{
printf("Hello Linux");
fflush(stdout); //立刻刷新,直接打印出结果
sleep(2);
return 0;
}
现在就会打印出1中(2)的那种结果啦
2.4 缓冲区存储位置
💢标准输入输出流(stdin、stdout、stderr)和文件流都是 FILE* 类型,它们在缓冲区管理中扮演了重要角色。当我们打开一个文件时,系统会返回一个 FILE* 类型的指针,文件的读写和关闭操作都需要该指针作为参数。
内部结构
💢struct FILE 封装了文件描述符(fd)、缓冲区以及缓冲区刷新策略。这使得文件操作变得高效和透明,开发者无需关心低级别的文件操作细节。
2.5 总结
💢缓冲区是提高系统数据读写效率的重要机制。理解和有效利用缓冲区及其刷新策略,可以显著提升程序性能和资源利用效率。
3. 进度条的实现
💢我们知道了上面两个知识点,\r将光标回到最开始就可以将其覆盖掉,所以我们利用这个特点可以写一个倒计时小程序,那我们先写一个10秒以内的倒计时小程序,这样方便更好来实现进度条
3.1 倒计时的实现
#include <stdio.h>
#include <unistd.h>
int main()
{
int cnt=10;
while(cnt--)//循环判断的条件
{
printf("倒计时:%-2d\r",cnt);//输出倒计时的数字, %-2d把两位覆盖,\r刷新数据
sleep(1);//间隔一秒
fflush(stdout);//刷新缓冲区
}
printf("\n");//刷新缓冲区,换行
return 0;
}
3.2 进度条样式
- 主体样式为两个中括号包裹,中间
=>
推进的方式呈现,比如:[======]
- 主体右侧中括号位置保持不变,中间元素不断推进,比如:
[= ]
- 显示当前加载进度,用
[num%]
显示,num
随着进度条的不断推进而变化- 显示加载样式,可以利用一个旋转的字符,例如
[\]
的样式,顺时针不断旋转
大约呈现状态为:[========>] [15%] [\]
3.3 采用多文件
文件存放在 processbar 目录中
- process.h :函数声明
- process.c :进度条逻辑
- main.c :函数调用
3.4 代码如下:
🍉版本一:
process.h
#pragma once
//进度条函数
void Progress();
process.c
#include "process.h"
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#define NUM 101
#define STYLE '='
void Process()
{
const char *lable = "|/-\\";
int len = strlen(lable);
char bar[NUM];
memset(bar, '\0', sizeof(bar));
int cnt = 0;
while(cnt <= 100)
{
printf("[%-100s][%d%%][%c]\r", bar, cnt, lable[cnt%len]);
fflush(stdout);
bar[cnt] = STYLE;
cnt++;
if(cnt == NUM){
bar[cnt - 1] = '\0';
printf("[%-100s][%d%%][%c]\r", bar, cnt-1, lable[cnt%len]);
break;
}
bar[cnt] = '>';
usleep(100000);
}
printf("\r\n");
}
main.c
#include "process.h"
int main()
{
process();
return 0;
}
🥝版本二:
process.h
#pragma once
void FlushProcess(double total, double current);
process.c
#include "process.h"
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#define NUM 101
#define STYLE '='
#define POINT '.'
#define SPACE ' '
const int pnum = 6;
// 真实的进度条,一个根据具体的情况,比如下载的量来动态刷新进度
void FlushProcess(double total, double current)
{
// 1. 更新当前进度的百分比
double rate = (current / total) * 100;
// printf("test: %.1lf%%\r",rate);
// fflush(stdout);
// 2. 更新进度条主体
char bar[NUM]; // 这里 1% 更新一个等号
memset(bar, '\0', sizeof(bar));
int i = 0;//定义放到了外面,因为C99之前的for(int i; )不支持
for (i = 0; i < (int)rate; i++)
{
bar[i] = STYLE;
}
// 3. 更新旋转光标或者其他风格
static int num = 0;
num++;
num %= pnum;
char points[pnum + 1];
memset(points, '\0', sizeof(points));
//直接用上面的i
for (i = 0; i < pnum; i++)
{
if (i < num) points[i] = POINT;
else points[i] = SPACE;
}
// 4. test && printf
printf("[%-100s][%.1lf%%]%s\r", bar, rate, points);
fflush(stdout);
// sleep(1);
}
main.c
#include "process.h"
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <time.h>
const int pnum = 6;
typedef void(*flush_t)(double total, double current); // 刷新的函数指针类型
const int base = 100;
double total = 2048.0; // 2048MB
double once = 0.1; // 0.5MB
// 进度条的调用方式
// 模拟下载行为
void download(flush_t f)
{
double current = 0.0;
while (current < total)
{
int r = rand() % base + 1;
double speed = r * once;
current += speed;
if (current >= total) current = total;
usleep(10000);
// 更新除了本次新的下载量
// 根据真实的应用场景,来进行动态刷新
//Process(total, current);
f(total, current);
// printf("test: %.1lf/%.1lf\r", current, total);
// fflush(stdout);
}
printf("\n");
}
int main()
{
srand(time(NULL));
download(FlushProcess);
download(FlushProcess);
download(FlushProcess);
return 0;
}
进度条演示
4. Linux上git的操作
4.1 上传
(1) 首先是在自己的gitee上新建远端仓库,然后复制仓库的地址。
(2) git clone
(3)git add [file name]
注:git add . 可以新增当前目录下所有未增文件
(4)git commit -m " "
-m选项代表的是本次的提交日志
" " 里面应该表明提交日志、描述改动的详细内容,务必培养这个好习惯。
(5)git push
4.2 git操作时遇到的问题
(1)为什么会提交失败
解决:
提示:Please tell me who you are.
翻译过来就是:请告诉我你是谁。
就是说这里git无法识别你是谁,你需要告诉 git 你的身份。
其实提示已经告诉了你的问题:git config --global user.email "you@example.com" git config --global user.name "Your Name"
我们直接用上面指令输入自己gitee上对应的邮箱名和名字即可
注:
💢💢git config命令的–global参数,用了这个参数,表示你这台机器上所有的Git仓库都会使用这个配置,当然也可以对某个仓库指定不同的用户名和Email地址。
而且我们也可以通过 git config -l 来查看配置信息
(2)push时出现的警告
warning: push.default is unset; its implicit value is changing in
Git 2.0 from 'matching' to 'simple'. To squelch this message
and maintain the current behavior after the default changes, use:
git config --global push.default matching
To squelch this message and adopt the new behavior now, use:
git config --global push.default simple
See 'git help config' and search for 'push.default' for further information.
(the 'simple' mode was introduced in Git 1.7.11. Use the similar mode
'current' instead of 'simple' if you sometimes use older versions of Git)
什么意思呢?简单来说:
git push其实有多种模式,不同的模式对应着不同的操作:今天我们简单看看上面提到的matching(匹配模式)和simple(简单模式):
matching
:这是 Git 之前默认的模式simple
:这是一种适合初学者的模式,并且在 Git 2.0 之后默认就是这个模式💢💢其具体区别我并不关心,什么分支匹配、什么推送拉取,先把 git 用起来,其他问题以后再说
matching
是之前默认的,正常使用肯定是没问题的;而simple
是现在默认的,我看了一下,大概就是在推送时会进行一些检查,更适合新手。我在这里还是选用之前默认的matching
模式💢💢实际上这并不是一个报错,而是一个提示,你会发现在警告(warning)之后依然可以正常输入用户名和密码,因为他默认已经帮你选好了,就是
simple
模式,现在只是提醒你一下而已
解决方法:
- 既然我们知道了这只是一个提示信息,而并非报错,那解决起来就很简单了
- 根据 warning 的提示,选定一个默认的推送模式即可
# 执行此命令以选择旧版 git 的默认推送模式,并消除提示信息
git config --global push.default matching
# 执行此命令以选择旧版 git 的默认推送模式,并消除提示信息
git config --global push.default matching
- 任意选择一个模式后,再进行
git push
指令的操作,就不会有警告了
4.3 git的小知识
(1)git status 可以查看git仓库状态
比如当我们新建了一个 test.c文件
‘
若git add后则变成
(2)git log 查看提交日志
(3) git pull
当我们 git push 出现下面的问题时
这个可能是由于 git 远端仓库 与本地仓库不一致的原因,因此我们可以用git pull 在git push 之前
📖总结
以上就是进度条实现 && Linux下git 的远程上传的全部内容啦!!!
💞 💞 💞那么本篇到此就结束,希望我的这篇博客可以给你提供有益的参考和启示,感谢大家支持!!!祝大家天天开心