四联光电智能照明论坛

 找回密码
 立即注册
搜索
热搜: 活动 交友 discuz
查看: 2983|回复: 0
打印 上一主题 下一主题

Linux终端调整输出色彩和光标位置的简单示例

[复制链接]
  • TA的每日心情
    开心
    2018-11-9 08:52
  • 241

    主题

    691

    帖子

    7652

    积分

    论坛元老

    Rank: 8Rank: 8

    积分
    7652
    跳转到指定楼层
    楼主
    发表于 2016-12-27 15:30:28 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
    相关的知识

    实现的原理并不复杂,借助了Linux的 ANSI 颜色控制序列,只要在打印输出的过程中使用这些序列,就能做到彩色的字符、背景色输出,以及控制光标在打印时的位置。

    简单的代码示例

    以下代码都在树莓派的Raspbian系统中编译运行过。

    #include <stdio.h>
    #include <unistd.h>

    int main(void)
    {
        // str 字符串里包含了调整字体为红色以及光标隐藏的序列
        char str[] = "\033[31mplease wait...\033[?25l";
        char ch[3][5] = {"\\","-","/"};
        int  times = 20;

        fputs(str, stderr);
        while (times --)
        {
            fputs(ch[0], stderr);
            // 由于上面打印的字符串仅仅只占一个字符宽
            // 那么这里就只用将光标向左移动1格即可
            fputs("\033[1D", stderr);
            // 暂停 0.2 秒
            usleep(200000);
            // 调整好光标的位置后,直接打印内容,就能覆盖
            // 屏幕上之前的输出文本
            fputs(ch[1], stderr);
            fputs("\033[1D", stderr);
            usleep(200000);
            fputs(ch[2], stderr);
            fputs("\033[1D", stderr);
            usleep(200000);

        }
        // 这里建议打印一个换行,并且让光标否则程序运行结束之后,
        // 退回到shell,shell的提示符会紧跟着“please wait”字符串
        // 显示出来。
        // 此外,如果不让隐藏的光标显现,回到shell中后光标依然是
        // 隐藏状态,但此时直接按下回车键,键入空命令,在下一
        // 行命令中光标又会自动显现。
        fputs("\n\033[?25h", stderr);

        return 0;
    }
    上面代码的运行效果,是想在同一行行末的固定一个字符的位置,依次输出“\”“-”“/”并快速循环,给人以旋转的感觉。如果有读者在Kali Linux中启动“msfconsole”,则会看到程序启动中,在某一行提示信息的行末就使用了这个效果。

    输出效果示意:

    please wait...\
        ↓
    please wait...-
        ↓
    please wait.../
    如果我们是用 fputc 函数打印单个字符,那么这光标位置调整仍然有效:

    #include <stdio.h>
    #include <unistd.h>

    int main(void)
    {
        char str[] = "\033[31mplease wait...\033[?25l";
        char ch[3] = {'\\', '-', '/'};
        int  times = 20;

        fputs(str, stderr);
        while (times --)
        {
            fputc(ch[0], stderr);
            fputs("\033[1D", stderr);
            usleep(200000);
            fputc(ch[1], stderr);
            fputs("\033[1D", stderr);
            usleep(200000);
            fputc(ch[2], stderr);
            fputs("\033[1D", stderr);
            usleep(200000);  
        }
        fputs("\n\033[?25h", stderr);

        return 0;
    }
    ANSI 色彩控制序列的简介

    \033[0m 关闭所有属性
    \033[1m 设置高亮度
    \033[4m 下划线
    \033[5m 闪烁
    \033[7m 反显
    \033[8m 消隐
    \033[30m -- \33[37m 设置前景色
    \033[40m -- \33[47m 设置背景色
    字背景颜色:40----------49
    40:黑
    41:深红
    42:绿
    43:黄色
    44:蓝色
    45:紫色
    46:深绿
    47:白色
    字颜色:30-----------39
    30:黑
    31:红
    32:绿
    33:黄
    34:蓝色
    35:紫色
    36:深绿
    37:白色

    \033[nA 光标上移n行
    \033[nB 光标下移n行
    \033[nC 光标右移n行
    \033[nD 光标左移n行
    \033[y;xH设置光标位置
    \033[2J 清屏
    \033[K 清除从光标到行尾的内容
    \033[s 保存光标位置
    \033[u 恢复光标位置
    \033[?25l 隐藏光标
    \033[?25h 显示光标
    其中,示例代码里面用到的“\033[31m”表示的是字体颜色设置为红色。详细的使用说明可用 man 命令进行查看:

    $ man console_codes
    注意,这些控制序列仅仅在Linux下有效,在windows的控制台程序中无效。而windows的控制台程序则可以通过“gotoxy”函数来调整光标的位置。

    一些要注意的地方

    为什么是用stderr而非stdout作为输出流?
    我在阅读其他人介绍 ANSI 序列的相关博客中,看到很多作者都是直接使用 printf 函数打印输出的,我将他们的代码在树莓派上进行了编译运行,跟文中的描述并无二致,颜色、光标的移动、清屏等效果都复现出来了。

    但是,需要注意,printf函数是将数据写到标准输出,而Linux遵循了一个标准I/O缓冲的一个惯例——标准错误不带缓冲,打开至终端设备的的流是行缓冲的,其他流是全缓冲的。显示器作为标准输出设备,这对 printf 写到标准输出流(stdout)的影响就是,stdout 流是带有行缓冲的。

    进一步的,行缓冲的特点:

    行缓冲。在这种情况下,当在输入和输出中遇到换行符时,标准I/O库执行I/O操作。这允许我们一次输出一个字符(用标准I/O函数fputc),但只有在写了一行之后才进行实际I/O操作。当流涉及一个终端时(如标准输入和标准输出),通常使用行缓冲。

    对于行缓冲有两个限制。第一,因为标准I/O库用来收集每一行的缓冲区的长度是固定的,所以只要填满了缓冲区,那么即使还没有写一个换行符,也进行I/O操作。第二,任何时候只要通过标准I/O库要求从 (a)一个不带缓冲的流,或者 (b)一个行缓冲的流(它从内核请求需要数据)得到输入数据,那么就会冲洗所有行缓冲输出流。在 (b)中带了一个在括号中的说明,其理由是,所需的数据可能已在该缓冲区中,它并不要求一定从内核读取数据。很明显,从一个不带缓冲的流中输入(即 (a) 项)需要从内核获得数据。
    ……
    当一个进程正常终止时(直接调用 exit 函数,或从 main 函数返回),则所有带未写缓冲数据的标准I/O流都被冲洗,所有打开的标准I/O流都被关闭。

    ————摘自《UNIX环境高级编程(第3版)》
    如果我们写下如下代码来测试ANSI序列:

    #include <stdio.h>
    #include <unistd.h>
    int main(void)
    {
        char  str = "\033[31mplease wait...\033[?25l";
        char  ch[3] = {'\\', '-', '/'};

        printf("%s", str);
        printf("%c\033[1D", ch[0]);
        sleep(2);
        printf("%c\033[1D", ch[1]);
        sleep(2);
        printf("%c\033[1D", ch[2]);
        sleep(2);
        printf("\n");

        return 0;
    }
    编译运行之后,发现代码是先等待6秒,然后一下子就打印出了“please wait…/”(字体为红色)并退出。究其原因,这3条printf语句将数据送入 stdout 的行缓冲中,行缓冲区既没有写满,也没有写入换行符,导致 stdout 中的这些序列数据一直留在了行缓冲区,而并非我们的程序没有按顺序执行,直到进程结束,程序退出,标准I/O流都被冲洗,这才在屏幕上打印出来。

    在其他网站上看到下面一段代码,注意“right 19”那里就没有换行符,结果导致它和“left 10”的结果是一起打印出来(因为“left 10”打印了换行符)而不是先后打印,可以在打印“right 19”之后再调用一个fflush(stdout)冲洗缓冲区,就能看到“right 19”被打印出来。

    #include <stdio.h>
    #include <unistd.h>

    int main(void)
    {
        printf("\033[31mThe color,%s!\033[1m\n", "haha");
        printf("\033[31mThe color,%s!\033[4m\n", "haha");
        printf("\033[31mThe color,%s!\033[5m\n", "haha");
        printf("\033[31mThe color,%s!\033[7m\n", "haha");
        printf("\033[31mThe color,%s!\033[8m\n", "haha");
        printf("\033[31mThe color,%s!\033[0m\n", "haha");
        printf("\033[47;31mThe color,%s!\033[0m\n", "haha");
        printf("\033[47mThe color,%s!\033[0m\n", "haha");
        sleep(2);
        printf("\033[44m%s!\033[15A\n", "up 15");
        sleep(2);
        printf("\033[43m%s!\033[9B\n", "down 9");
        sleep(2);
        // 注意这里是没有打印换行符的!
        printf("\033[42m%s!\033[19C", "right 19");
        printf("right 19");
        sleep(2);
        printf("\033[41m%s!\033[10D", "left 10");
        printf("left 10\n");
        sleep(2);
        printf("\033[45m%s!\033[50;20H\n", "move to 50, x 20");
        printf("y50 x20");
        sleep(2);
        printf("\033[46m%s!\033[?25l\n", "hide cursor");
        sleep(2);
        printf("\033[42m%s!\033[?25h\n", "show cursor");
        sleep(2);
        printf("\033[43m%s!\033[2J\n", "clear screen");
        return 0;
    }
    由于标准错误流 stderr 不带缓冲,我们把输出写到它里面就不受换行符触发或者填满缓冲的限制,只要往里面写入数据,就会立即打印到屏幕上。

    色彩控制序列的作用范围
    只要我们在打印序列中使用了这些色彩控制序列,如果不主动取消,那么它将会一直作用在程序后续的输出打印上,比如那个字体红色属性,在代码里嵌在“please wait…”字符串中,但同样会影响后面“\”“-”“/”的色彩。而且前面也谈到,那个光标隐藏属性即使在程序结束后都还能影响到shell的光标显示。

    打印控制的窗口自适应性
    简而言之,我们在GUI的非登陆式shell打印的时候,程序是以当前窗体的界面大小来自适应光标的移动范围,清屏的时候色彩覆盖的范围。它并非按照我们实际屏幕的物理尺寸来调整输出。
    您需要登录后才可以回帖 登录 | 立即注册

    本版积分规则

    QQ|Archiver|手机版|小黑屋|Silian Lighting+ ( 蜀ICP备14004521号-1 )

    GMT+8, 2024-4-29 11:36 , Processed in 1.062500 second(s), 23 queries .

    Powered by Discuz! X3.2

    © 2001-2013 Comsenz Inc.

    快速回复 返回顶部 返回列表