Linux小程序(1)—— 简单进度条
本文记录在Linux中实现一个简单进度条程序。要完成此程序,需要用到前置知识;需要掌握vim的基础操作和Linux自动化构建工具makefile的使用。详细请查看文章:Linux(4)—— 基础开发工具
项目框架
进度条程序我们很熟悉,在手机电脑上玩游戏、下载安装软件等我们都能看到下方会有一个安装后加载的进度条。在开始该程序项目之前,我们需要理清楚设计的框架。
首先,要有main函数文件,即main.c文件。
然后,要有一个头文件,我们创建一个名为processBar.h的头文件。此外,程序的主要实现代码也可以单独放在一个文件里。我们再创建一个processBar.c的文件用于存放程序主要逻辑代码。
最后,运用我们前面学习到的Linux自动化项目构建工具 makefile(Makefile),我们需要再创建一个makefile文件。
综上所述,我们一共需要创建四个文件,自己选择一个适合的文件夹,创建上面的四个文件:\
touch main.c processBar.h processBar.c Makefile

创建好之后,我们打开头文件加入库:
#pragma once#include <stdio.h>
之后我们再打开Makefile进行配置:
processbar:pricessBar.c main.cgcc -o $@ $^
.PHONY:clean
clean:rm -f processbar
这样我们输入make指令就会生成processbar的程序,需要清除项目使用make clean即可。
我们来看一下Makefile里面的代码。
可以看到我们编译和清理一个名为processbar的程序。第一行为依赖关系;文件proceeBar.c和main.c两个文件时目标文件的依赖项,即processbar依赖于这两个源文件。
第二行 gcc -o $@ $ 是构建目标的命令。其中 $@是Makefile自动变量,代表目标文件名(即processbar)。而$^代表所有依赖文件,即(processBar.c 和 main.c源文件)
所以第二行的依赖方法等效于下面的代码:
gcc -o processbar processBar.c main.c
最后,.PHONY:clean:表示声明 clean 是一个伪目标:
伪目标不对应实际的文件,总是执行其命令防止目录中存在名为 clean 的文件时规则不执行。
最后的删除代码我们在 Linux(4)—— 基础开发工具 中有过演示,这里不再说明。
倒计时
在正式开始进度条程序之前,我们先来尝试设计部一个倒计时程序。这样方便我们理解构建项目。如果不想完成这部分,可以直接点击目录跳转到进度条项目。
下面我们创建一个 test.c 文件进行编写。
#include "processBar.h"int main()
{return 0;
}
1、打印数字
要看到倒计时,首先我们肯定要能看的到倒计时的数字。我们假设一共需要倒计时9秒钟。
#include "processBar.h"int main()
{int cnt = 9;while(cnt>=0){printf("%d",cnt);cnt--;}return 0;
}
编译后运行看一下,发现成功打印:
![]()
2、模拟时间
但是我们发现这是一下子全部打印出来了,所以我们需要模拟一下时间,让程序一秒钟打印一个数字;这里我们使用sleep。在Linux中,sleep的参数单位是秒,这和windows系统不同。
#include "processBar.h"int main()
{int cnt = 9;while(cnt>=0){printf("%d",cnt);fflush(stdut);//刷新打印cnt--;sleep(1);//间隔一秒}return 0;
}
我们再次运行就能成功看到每隔一秒打印一个数字了。
3、覆盖
不过倒计时并不会全部把数字显示出来;我们如何实现只看到一个数字?这就需要将前面已经打印的内容进行覆盖。前面我们在打印的时候是顺着后面打印的,因为光标出现在打印的数字后面。要想实现覆盖,就需要实现打印时的光标出现在打印的数字左边(前面)。我们这里使用\r,表示将光标移动到当前行的开头。这样我们就能成功实现覆盖操作。
#include "processBar.h"int main()
{int cnt = 9;while(cnt>=0){printf("%d\r",cnt);//使用/r将光标移动到当前行开头实现覆盖fflush(stdut);//刷新打印cnt--;sleep(1);//间隔一秒}return 0;
}
我们也可以加上在最后打印一个换行符(看着更舒服):
#include "processBar.h"int main()
{int cnt = 9;while(cnt>=0){printf("%d\r",cnt);fflush(stdut);//刷新打印cnt--;sleep(1);//间隔一秒}printf("\n");return 0;
}
4、打印两位数
如果我们需要倒计时的时间为两位数,我们将cnt的值改为10,再次运行看一下:

这个时候问题出现了,10的第二位0会一直保持在屏幕上。要解决这个问题,只需要做出如下改动:
#include "processBar.h"int main()
{int cnt = 10;while(cnt>=0){printf("%2d\r",cnt);fflush(stdut);//刷新打印cnt--;sleep(1);//间隔一秒}printf("\n");return 0;
}
我们将%d改为了%2d,这样表示按至少2个字符宽度输出整数。因为10开始是表示两个字符,它不是我们看到的10 ,而是一个1和一个0。所以我们使用%2d就可以打印两个字符,实现二位数的倒计时。以此类推,三位数,甚至四位数倒计时也可以实现。不过一般情况不会显示那么大的数字。
不过,还可以改进一点。我们发现按照上面的运行之后,打印的数字会停留在第二个字符位置。所以我们再改成 %-2d,让打印的数字进行左对齐,运行后发现没有问题。
#include "processBar.h"int main()
{int cnt = 10;while(cnt>=0){printf("%-2d\r",cnt);fflush(stdut);//刷新打印cnt--;sleep(1);//间隔一秒}printf("\n");return 0;
}
这样我们就成功实现了倒计时。
进度条
1、准备工作
下面我们打算在processbar函数里面进行主要功能的代码实现。所以我们需要在processbar.h文件中对该函数进行声明,在process.c文件中写入主要代码;最后要在main.c文件中实现调用。
所以在头文件中:
#pragma once#include <stdio.h>extern void processbar();
声明一个processbar函数。
在processBar.c文件中,先写好processbar函数;这里为了方便测试,我们先在函数中写一个打印语句:
#include "processBar.h"void processbar()
{printf("Hello processBar!\n");
}
最后在main函数中进行对processbar函数的调用:
#include "processBar.h"int main()
{processbar();return 0;
}
关于makefile文件的配置我们不做修改。
下面我们make一下,看能否正常运行:

一切正常。
下面我们直接实现processbar函数即可。
2、函数实现
我们可以将进度条分成100份,用字符来表示进度,每一个字符表示%1;在后面进度增加的时候,将光标移动到当前字符的左边,用新的字符将前面的覆盖,实现递增效果。
所以我们需要一个数组来存储这些字符内容,并通过循环来实现打印。
我们先定义进度条打印的字符,假设为#,设置数组的大小为102;打开头文件:
#pragma once#include <stdio.h>
#include <string.h>
#include <unistd.h>#define NUM 102
#define STYLE '#'//进度条字符extern void processbar();
打开函数文件,我们先使用while循环打印数组:
#include "processBar.h"void processbar()
{char bar[NUM];memset(bar,'\0',sizeof(bar))//初始化数组int cnt = 0;while(cnt <= 100){printf("%s\n",bar);//打印字符串bar[cnt++] = STYLE;sleep(1);//模拟间隔}
}
这里我们先用\n换行符查看一下是否实现增加。
make后运行一下,可以看到现在会一行一行地增加字符:

下面我们需要让其实现覆盖。
和前面的倒计时一样,我们需要使用\r来实现光标跳转。此外,还需要进行刷新。
\n为换行,光标跳转到开头并刷新,而\r是只跳转光标。没有\n,就没有立即刷新,因为显示模式是行刷新方式。所以我们还需要使用fflush刷新。
#include "processBar.h"void processbar()
{char bar[NUM];memset(bar,'\0',sizeof(bar))//初始化数组int cnt = 0;while(cnt <= 100){printf("%s\r",bar);//打印字符串fflush(stdout);//刷新 bar[cnt++] = STYLE;sleep(1);//模拟间隔}
}
我们再次运行就发现可以正常覆盖了。
3、usleep
我们发现一秒钟打印一个字符似乎有点太慢了,要观察程序到结束要耗时一分多钟。因此我们将sleep换成usleep可以让程序运行时间缩短。
usleep和sleep都是进行休眠。只不过usleep的参数单位是微秒。我们设置时间间隔为0.1秒,而进制关系为:1 s = 1000000微秒。
所以我们修改一下代码:
#include "processBar.h"void processbar()
{char bar[NUM];memset(bar,'\0',sizeof(bar))//初始化数组int cnt = 0;while(cnt <= 100){printf("%s\r",bar);//打印字符串fflush(stdout);//刷新 bar[cnt++] = STYLE;usleep(100000);//模拟间隔}
}
4、预留空白
我们为整个进度条加上[]框:
#include "processBar.h"void processbar()
{char bar[NUM];memset(bar,'\0',sizeof(bar));//初始化数组int cnt = 0;while(cnt<=100){printf("[%-100s]\r",bar);fflush(stdout);bar[cnt++] = STYLE;usleep(100000);}printf("\n");
}
加上-号表示左对齐。
5、显示百分比
除了字符显示进度,我们还需要设置一个百分比的数字显示。
#include "processBar.h"void processbar()
{char bar[NUM];memset(bar,'\0',sizeof(bar));//初始化数组int cnt = 0;while(cnt<=100){printf("[%-100s][%d%%]\r",bar,cnt);//%%表示%号fflush(stdout);bar[cnt++] = STYLE;usleep(100000);}printf("\n");
}
上面的进度条我们使用的是#来做加载字符;我们也可以改变风格,使用其他的字符来展示。例如=号或-号等等。
