【Linux】在Linux上写一个进度条小程序

简介: 【Linux】在Linux上写一个进度条小程序

一、前言


在前三篇文章中,我们分别学习了 vim 、gcc 以及 make/makefile 。而在今天,我们将基于前三节课认识的基础上,并结合一些与回车换行、缓冲区有关的知识,在 Linux 上写下一个简易的进度条小程序。


成品展示

6e984d0c795e437bbbe07954f66a8ae7.gif

今天的内容比较轻松,只需要了解两个知识点,这个小程序就很容易写出来了,让我们开始今天的学习。




二、理解 ‘\r’ 与 ‘\n’


C 语言中有很多字符,而字符大体分为两类:可显字符、控制字符。

控制字符不可显示,例如 \r 和 \n 就是控制字符。



而在我们平时打字时,一行写满了需要换行,但是新起一行有很多种,例如:


9ca8e379393112e13bd73aa280cb0969.png


这样虽然新起一行了,但是不是我们想要的结果。


我们通常新起一行是在第二行的最左端,但是对于这个结果其实有两个操作:


   跳转到第二行


   回到第二行的最左端


有了这个基本概念,再来谈 \r 和 \n 的作用:


   \r :回车 - 回到文本行的开头


   \n:换行 - 新起一行


所以,其实我们 平时泛指的换行实际上是 回车 + 换行 。


且在语言范畴下,例如 C 语言,换行就可以达到 回车 + 换行 的效果。在平常,这一操作还是两个步骤。



三、行缓冲


行缓冲这个概念认识。


1、提出问题


首先先了解一下两个库函数:


  • sleep : Linux 下的休眠函数,单位是秒。头文件为 #include <unistd.h>
  • fflush :刷新缓冲区



代码1:

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


现象:


d49e105c66584d77b9de485c78755f1e.gif


分析:


光标停留在文本行的开头,但是字符串没有被打印。

反而像是 sleep 函数先起作用,然后 printf 函数再从光标处开始打印。

打印完之后,shell 提示符紧跟着字符串后显示。



代码2:

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


现象:


35d338cd43e648648f33bc7fea60afbd.gif


分析:

printf 打印的字符串先显示在终端上,光标位于字符串的下一行。

sleep 函数使程序休眠 3 秒后,shell 提示符从光标位置开始显示。


代码3:

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



现象:


b1e8f99bd43b47c2aac4b5410ac6a0e5.gif


分析:

printf 打印的字符串没有显示到终端,光标一直停留在该打印字符串的一行

sleep 函数休眠三秒后,shell 提示符直接打印在了屏幕上。

并没有看到字符串。


观察上面的现象,我们提出几个问题:


  1. 代码 1 好像是先执行了 sleep ,在执行 printf ,是这样吗?
  2. 代码 2 加上了 ‘\n’ ,字符串一开始就显示了,为什么?
  3. 代码 3 好像什么都没打印,这是为什么?

在解答这些问题之后,我们先了解一下行缓冲。




2、认识行缓冲


在内存中预留了一块空间,用来缓冲输入或输出的数据,这个保留的空间被称为缓冲区。

我们之前或多或少都听说过缓冲区。


在代码 1 中,由于程序是按照数据执行的,所以必定是先执行 printf 。


但是数据没有显示,所以这时候,数据就一定被保存在某个位置,保存的位置就是缓冲区。


而要让数据显示,是需要刷新缓冲区的。


行缓冲是缓冲区刷新策略的一种,在行缓冲模式下,当输入和输出中遇到 ‘\n’ 换行时,就会刷新缓冲区 。


有了这个概念,我们继续分析问题。




3、解答与拓展


解答


问题1:代码 1 好像是先执行了 sleep ,在执行 printf ,是这样吗?


   当然不是。


   由于程序是按照顺序执行的,所以必定是先执行完 printf 在执行 sleep 。

   而数据没有被显示出来的原因是:数据保存在缓冲区中,但是没有主动刷新,当程序退出后,保存在缓冲区中的数据被自动刷新出来了。


   所以才会造成这种现象。


问题2:代码 2 加上了 \n ,字符串一开始就显示了,为什么?


   这里由于是直接往显示器上打印,所以采用的刷新方式为行缓冲。


   所以执行碰到 ‘\n’ 时,就会把在缓冲区中的(换行符之前)的内容全部刷新出来。


   所以这段代码一开始就会有数据显示,然后再 sleep 休眠。


问题3:代码 3 好像什么都没打印,这是为什么?


   之前说过 \r 是换行,所以当 printf 遇到 \r 时,就把光标移到开头。


   sleep 睡眠后,当程序退出,shell 打印提示符时,就覆盖了字符串。


拓展 :


数据真的是临时保留在缓冲区里的吗?光标如何理解?


我们用一段代码来理解这两个问题:


#include <stdio.h>
#include <unistd.h>
int main()
{
    printf("hello xxx\r");
    fflush(stdout);
    sleep(3);
    return 0;
}


现象:


18048ba6effa4e04a432a1a755346c2f.gif


观察现象,我们发现当我们使用 fflush 主动刷新缓冲区后,数据就显示在了屏幕上;且因为 ‘\r’ 的原因,光标指向字符串开头;当打印 shell 提示符时,就直接从光标位置开始覆盖。


所以对于这两个问题,我们已经得到了答案:

   数据被临时保存在于缓冲区中,通过刷新就可以显示

   数据是从光标位置开始打印的。


一句话理解光标:光标和显示器匹配,光标在哪里,显示器打印的时候就从哪里开始打印 。



4、倒计时


基于对上面的理解,我们先实现一个简单的倒计时。


倒计时就是在屏幕上不断显示数字,每次在同一位置显示,并将之前的数据覆盖。

既然是每次要从头开始覆盖,那么就可以用 ‘\r’ 来实现每次回到行首,并且可以通过相应的格式化控制显示多位打印。


但是 ‘\r’ 不会主动刷新,所以要用 fflush 函数主动刷新缓冲区。


在每次刷新之后,使用 sleep 函数,间隔一定的时间。


由此,我们可以很轻松写出代码,例如写一个从 10 开始的倒计时:

#include <stdio.h>
#include <unistd.h>
int main()
{
    int i = 10;
    for (; i >= 0; i--) {
        // 位宽控制,\r 回到开头
        printf("%2d\r", i);
        fflush(stdout); // 主动刷新
        sleep(1); // 休眠
    }
    printf("\n"); // 换行,打印提示符
    return 0;
}


9acddde30df547618b63008c4e1d9733.gif




四、进度条


好了,接下来进入正题,我们开始写 进度条


进度条样式 :


   主体样式为两个中括号包裹,中间 => 推进的方式呈现,比如:[======>]

   主体右侧中括号位置保持不变,中间元素不断推进,比如:[=> ]

   显示当前加载进度,用 [num%] 显示,num 随着进度条的不断推进而变化

   显示加载样式,可以利用一个旋转的字符,例如 [\] 的样式,顺时针不断旋转


大约呈现状态为:[========>] [15%] [\]


采用多文件 :


文件存放在 proc 目录中


   proc.h :函数声明

   proc.c :进度条逻辑

   main.c :函数调用


makefile 准备 :


由于采用多文件,所以依赖关系可以写成依赖文件列表的样式:



f4caa044166613c78e38062d09f3a35b.png


分块逻辑

  1. 进度条主体


预留进度条大小为 100 个 = ,外加 1 个 > ,加上保存 '\0' 的位置,用数组存储为 102 个单位。

进度条是一行中的,所以需要用到 '\r' ,每次都需要使用 fllush 主动刷新缓冲区。


每次刷新出数据之后,将 = 填充到数组中,并且显示 > 。在最后一次显示时,控制 > 不要显示。

然后休眠一小会。由于休眠用 sleep 函数太慢。所以可以用 usleep 函数休眠,usleep 函数的参数单位是微秒。


根据这个写出代码:


26feb29f64af769a4848398b10c24753.png

bf0e416605aa4d40a0e35238b4079787.gif


  1. 百分比显示

%% 显示为一个 % ,而 %d 为数字,这步很简单,只要在 printf 语句中加上内容:


printf("[%-100s][%d%%]\r", bar, i);


  1. 旋转光标

光标旋转方向为顺时针旋转,那么旋转时就可以用数组保存。


旋转每次显示内容分别为 | / - \\\ 代表一个 \ ,因为和 \ 结合的会被解析为转义字符,将其保存到字符串中。


而由于字符串一共就四个字符,所以输出的时候需要控制输出位置。

代码:

const char* str = "|/-\\"; // 字符串
printf("[%-100s][%d%%][%c]\r", bar, i, str[i % 4]); // 输出语句


完整代码

proc.h

#pragma once 
#include <stdio.h>
extern void process();



proc.c

#include "proc.h"
#include <string.h>
#include <unistd.h>
#define SIZE 102 // 数组大小
#define STYLE '='
#define FLAG '>'
void process()
{
    const char* str = "|/-\\";
    char bar[SIZE];
    memset(bar, '\0', sizeof(bar));
    int i = 0;
    while (i <= 100) {
      printf("[%-100s][%d%%][%c]\r", bar, i, str[i % 4]); // 格式控制
      fflush(stdout); // 刷新
      bar[i++] = STYLE; // 填充数据
      if (i != 100) {
          bar[i] = FLAG; // 如果不是最后一次则显示 >
      } 
      usleep(100000); // 休眠
    }
    printf("\n");
}


test.c

#include "proc.h"
int main()
{
    process();
    return 0;
}


进度条展示

089c65c1613143ef9e341928eb3d3b99.gif



五、结语



到这里,本篇博客就到此结束了。


今天的内容相对来说还是很简单的,我们的今天核心就是了解 ‘\r’ 和 ‘\n’ 的区别,并认识了行缓冲。只要了这两块知识,写起进度条就很轻松了。


大家感兴趣也可以下去试一试。


如果觉得 a n d u i n anduin anduin 写的不错的话,可以 点赞 + 收藏 + 评论 支持一下哦!我们下期见~




相关文章
|
6月前
|
小程序 Linux 开发工具
【Linux】Linux 开发工具(vim、gcc/g++、make/Makefile)+【小程序:进度条】-- 详解
【Linux】Linux 开发工具(vim、gcc/g++、make/Makefile)+【小程序:进度条】-- 详解
|
3月前
|
Linux 开发工具
linux下使用gcp拷贝数据的时候显示进度条
linux下使用gcp拷贝数据的时候显示进度条
28 2
|
4月前
|
小程序
【亲测有效】支持横竖屏 微信小程序video禁止进度条拖动,微信小程序遮罩进度条,
【亲测有效】支持横竖屏 微信小程序video禁止进度条拖动,微信小程序遮罩进度条,
84 1
【亲测有效】支持横竖屏 微信小程序video禁止进度条拖动,微信小程序遮罩进度条,
|
5月前
|
小程序 Linux C语言
Linux小程序 —— 进度条
Linux小程序 —— 进度条
89 6
|
5月前
|
Linux
【make/Makefile】Linux下进度条的设计与实现
【make/Makefile】Linux下进度条的设计与实现
|
6月前
|
数据可视化 小程序 Linux
【Linux】6. 实现进度条和git基本认识和使用
【Linux】6. 实现进度条和git基本认识和使用
60 4
|
网络协议 Linux C语言
linux 系统获取网络ip, mask, gateway, dns信息小程序
net_util.c          #define WIRED_DEV                   "eth0"     #define WIRELESS_DEV                "ra0"               #define PPPOE_DEV                   "ppp0" #define DEBUG_PRT(fmt, arg.
1317 0
|
3天前
|
Linux
在 Linux 系统中,“cd”命令用于切换当前工作目录
在 Linux 系统中,“cd”命令用于切换当前工作目录。本文详细介绍了“cd”命令的基本用法和常见技巧,包括使用“.”、“..”、“~”、绝对路径和相对路径,以及快速切换到上一次工作目录等。此外,还探讨了高级技巧,如使用通配符、结合其他命令、在脚本中使用,以及实际应用案例,帮助读者提高工作效率。
18 3
|
3天前
|
监控 安全 Linux
在 Linux 系统中,网络管理是重要任务。本文介绍了常用的网络命令及其适用场景
在 Linux 系统中,网络管理是重要任务。本文介绍了常用的网络命令及其适用场景,包括 ping(测试连通性)、traceroute(跟踪路由路径)、netstat(显示网络连接信息)、nmap(网络扫描)、ifconfig 和 ip(网络接口配置)。掌握这些命令有助于高效诊断和解决网络问题,保障网络稳定运行。
16 2
|
11天前
|
缓存 监控 Linux

热门文章

最新文章