C语言写一个终端进度条
这个功能挺简单的,主要有以下两点:
- 如何获取终端宽度
- 如何让字符在原地闪烁
如何获取终端宽度
这里用到了设备控制接口函数ioctl()
,下面简单的介绍一下这个函数的用法:
ioctl
是一个在Unix和类Unix系统中用于设备驱动程序的设备特定操作的系统调用。它的名称代表“输入/输出控制”,但实际上它可以用于任何设备,不仅仅是输入/输出设备。
在C语言中,ioctl
函数的原型如下¹:
#include <sys/ioctl.h>
int ioctl(int fd, int cmd, ...);
这里,fd
是一个打开的文件描述符,cmd
是设备特定的操作命令,后面的可变参数...
取决于cmd
指定的操作。
ioctl
函数执行成功时返回0,失败则返回-1并设置全局变量errno
值。可能的错误包括:
EBADF
:fd
不是一个有效的描述符。EFAULT
:argp
引用了一个无法访问的内存区域。EINVAL
: 请求或argp
无效。ENOTTY
:fd
没有与字符特殊设备关联。ENOTTY
: 指定的请求不适用于描述符d
引用的对象类型。
更详细的内容可以查询man手册或者其他博客!!这个函数还是挺复杂的!
对于判断终端大小的需求,我们有以下的使用方式:
struct winsize w;ioctl(STDOUT_FILENO, TIOCGWINSZ, &w); // 获取终端大小并且存储到w中int barWidth = w.ws_col - 10; // 减去10是为了放置越界
winsize
这个结构体位于termios.h
头文件内
// /usr/include/asm-generic/termios.h
Struct winsize
{unsigned short ws_row; /* rows, in character */unsigned short ws_col; /* columns, in characters */unsigned short ws_xpixel; /* horizontal size, pixels (unused) */unsigned short ws_ypixel; /* vertical size, pixels (unused) */
};
使用这个结构体记录窗口终端大小。
如何让字符在原地闪烁
要明白这一点,我们首先要知道两个前置知识:
printf
的输出条件- 终端中的光标
printf的输出条件
没错,printf
也有输出条件,如下:
- 遇到换行符
\n
- 进程终止
- 遇到
fflush
函数
单纯的使用printf
并不会直接输出到终端上,会先放在缓冲区,只有遇到换行符才会输出!!!
终端中的光标
我们可以通过'\r'
来控制缓冲区中的光标,这样,当我们输出进度条之后,光标又会回到进度条最一开始的位置,这样就可以使得进度条看不见了。
如下:
// 光标置于开头
// progressPercent是进程百分比
printf("\r%3d%% [", progressPercent);
整个函数
// 用法,输入进度值(小数)
// 此函数用于输出一次进度条
void printProgress(float progress) {struct winsize w;ioctl(STDOUT_FILENO, TIOCGWINSZ, &w);int barWidth = w.ws_col - 10; // -10 防止越界int progressPercent = (int) (progress * 100.0); // 计算出百分值printf("\r%3d%% [", progressPercent);int pos = barWidth * progress; // 包含隐式类型转换for (int i = 1; i < barWidth; ++i) {if (i < pos) {printf("#");} else {printf(" ");}fflush(stdout); // 输出完所有内容后刷新缓冲区,代码执行到这里才会有一次输出}printf("]"); // 最后补上后面的括号
}
这里再写一个main
函数用于测试:
int main() {for (int i = 0; i <= 100; ++i) {printProgress(i / 100.0);sleep(1);}printf("\n");return 0;
}
因为作者在项目开发时使用的是自用的通用头文件,代码中并没有包含头文件,请读者自行添加。
执行效果
vscode执行效果:
ssh客户端执行效果:
读者可以给进度条加以改进,作为下层代码,方便在上层代码上调用(例如文件传输C/S)。
:wq 拜拜~~~~