linux环境下c语言程序模拟终端输入

文章介绍了如何在Linux环境下,通过C语言编写程序来改变终端属性,允许使用Backspace键删除输入错误的字符。作者提供了一个完整的代码示例,展示了如何利用tcgetattr和tcsetattr函数禁用标准输入模式,并处理缓冲区。此外,代码还实现了Tap键的补全功能,通过链表存储搜索范围并进行匹配。
摘要由CSDN通过智能技术生成

概要

linux环境下c语言程序输入

linux环境下c语言写的程序,在调用scanf进行输入时,如果输错了字符,想使用Backspace键进行删除是不行的,会打印^H之类的乱码。希望能使用退格键之类的话就需要我们更改终端属性,然后自己处理缓冲区。

更改终端属性

tcgetattr、tcsetattr

#include <termios.h>
// 初始化模拟终端、查找表、控制台前缀
void arco_terminal_init()
{
    struct termios term;
    tcgetattr(STDIN_FILENO, &term);
    // 禁用标准输入模式和显示输入字符
    term.c_lflag &= ~(ICANON | ECHO);
    term.c_cc[VMIN] = 1;
    term.c_cc[VTIME] = 0;
    tcsetattr(STDIN_FILENO, TCSANOW, &term);
    // 刷新缓冲区强制输出
    fflush(stdout);
}

这部分我没细看,工作了要搬砖,有空慢慢研究吧

完整代码示例

这是我封装的一个支持backspace删除和tap键补全的输入函数

/*
 * author: arco
 * date  : 2023.6.24
 * 使用方法:
 * 1. 调用arco_terminal_add_search_str函数将目标字符串添加到查找表
 * 2. arco_terminal_set_input_perfix设置控制台打印前缀(可不选)
 * 3. 调用arco_terminal_input当做scanf函数接收输入
 */

#include <stdio.h>
#include <termios.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>

#define NOT_MATCH    -1
#define PERFIX_MATCH  0

/*
 * 链表定义区start
 */

typedef struct SearchListNode {
    char* str;
    struct SearchListNode* before;
    struct SearchListNode* next;
} SearchListNode;

typedef struct SearchList {
    SearchListNode* head;
    SearchListNode* end;
    int length;
} SearchList;

SearchListNode* init_search_list_node(const char* src_str)
{
    SearchListNode* node;
    node = (SearchListNode*) malloc(sizeof(SearchListNode*));
    node->before = (SearchListNode*) malloc(sizeof(SearchListNode));
    node->next = (SearchListNode*) malloc(sizeof(SearchListNode));
    if (src_str != NULL) {
        node->str = (char*) malloc((strlen(src_str) + 1) * sizeof(char));
        // strcpy函数自动添加\0
        strcpy(node->str, src_str);
    }
    else {
        node->str = NULL;
    }
    return node;
}

SearchList* init_search_list()
{
    SearchList* list = (SearchList*) malloc(sizeof(SearchList));
    list->head = (SearchListNode*) malloc(sizeof(SearchListNode*));
    list->end = (SearchListNode*) malloc(sizeof(SearchListNode*));
    list->head->before = NULL;
    list->head->next = list->end;
    list->end->next = NULL;
    list->end->before = list->head;

    list->length = 0;

    return list;
}

void search_list_add_node(SearchList* list, SearchListNode* list_node)
{
    SearchListNode* p = list->end->before;
    p->next = list_node;
    list_node->before = p;
    list->end->before = list_node;
    list_node->next = list->end;
    list->length++;
}

void search_list_add_str(SearchList* list, const char* src_str)
{
    SearchListNode* node = init_search_list_node(src_str);
    search_list_add_node(list, node);
}

void print_search_list(SearchList* list)
{
    SearchListNode* p = list->head->next;
    while (p->next != NULL) {
        printf("%s\t", p->str);
        p = p->next;
    }
}

/*
 * 链表定义区end
 */



/*
 * 匹配函数定义区start
 */

int g_arco_terminal_init_flag = 0;
char g_console_perfix[16];
SearchList* g_search_scope;

int str_match(const char* src_str, char* dst_str)
{
    int i, ret = PERFIX_MATCH;
    // 异常情况校验
    if (src_str == NULL || dst_str == NULL) return NOT_MATCH;
    if (strlen(src_str) > strlen(dst_str)) return NOT_MATCH;
    if (strlen(dst_str) == 0) return NOT_MATCH;
    // 按字符匹配
    for (i = 0; i < strlen(src_str); i++) {
        if (src_str[i] != dst_str[i]) {
            return NOT_MATCH;
        }
    }

    return ret;
}

int get_match_str_list(SearchList* list, SearchList* match_str_list, const char* src_str)
{
    int ret, match_num = 0;
    SearchListNode* p = list->head->next;
    while (p->next != NULL) {
        ret = str_match(src_str, p->str);
        if (ret == PERFIX_MATCH) {
            search_list_add_str(match_str_list, p->str);
            match_num++;
        }
        p = p->next;
    }
    return match_num;
}

int tap_search(char* arco_buffer, char *search_result, char pre_c)
{
    int match_num = 0;
    // 用链表替换二维数组
    SearchList* match_str_list = init_search_list();
    // 查找链表中所有匹配结果
    match_num = get_match_str_list(g_search_scope, match_str_list, arco_buffer);

    if (match_num == 0) return 0;
    else if (match_num == 1) {
        strcpy(search_result, match_str_list->head->next->str);
    }
    else {
        if (pre_c == '\t') {
            printf("\n");
            print_search_list(match_str_list);
            printf("\n");
        }
        // free先不写
    }

    return match_num;
}

/*
 * 匹配函数定义区end
 */



/*
 * 模拟终端输入工具区start
 */

// 初始化模拟终端、查找表、控制台前缀
void arco_terminal_init()
{
    struct termios term;
    tcgetattr(STDIN_FILENO, &term);
    // 禁用标准输入模式和显示输入字符
    term.c_lflag &= ~(ICANON | ECHO);
    term.c_cc[VMIN] = 1;
    term.c_cc[VTIME] = 0;
    tcsetattr(STDIN_FILENO, TCSANOW, &term);
    // 刷新缓冲区强制输出
    fflush(stdout);
    // 初始化控制台前缀
    memset(g_console_perfix, '\0', sizeof(g_console_perfix));
    // 初始化搜索范围链表
    g_search_scope = init_search_list();

    g_arco_terminal_init_flag = 1;
}

// 设置控制台输入前缀, 不设置即没有
int arco_terminal_set_input_perfix(char* console_perfix)
{
    if (strlen(console_perfix) > sizeof(g_console_perfix)) {
        printf("target console perfix too long!\n");
        return -1;
    }
    memset(g_console_perfix, '\0', sizeof(g_console_perfix));
    strcpy(g_console_perfix, console_perfix);
}

// 添加字符串到查找表
int arco_terminal_add_search_str(char* src_str)
{
    if (g_arco_terminal_init_flag != 1) {
        arco_terminal_init();
    }
    search_list_add_str(g_search_scope, src_str);
}

// 模拟终端输入函数
int arco_terminal_input(char* ipt_str)
{
    if (g_arco_terminal_init_flag != 1) {
        arco_terminal_init();
    }

    int i = 0, j;
    char c, pre_c = '\0', arco_buffer[32];

    memset(arco_buffer, '\0', sizeof(arco_buffer));
    printf("%s", g_console_perfix);
    while (1) {
        c = getchar();
        // Backspace: 退一格、用空格清除一个字符、再退一格, 指针减1
        if (c == '\b') {
            printf("\b \b");
            arco_buffer[i - 1] = '\0';
            i--;
        }
        else if (c == '\n') {
            printf("\n");
            break;
        }
            /*
             * tap键有3个逻辑
             * 1.匹配到唯一结果:直接打印
             * 2.匹配到多结果:第二次及以后敲时打印
             * 3.匹配不到:不做处理
             */
        else if (c == '\t') {
            int match_num;
            char search_result[16];
            memset(search_result, '\0', sizeof(search_result));
            match_num = tap_search(arco_buffer, search_result, pre_c);
            // 2的处理交给tap_search函数, 3不做处理, 因此这里仅处理1
            // 1的处理
            if (match_num == 1) {
                for (j = 0; j < i; j++) {
                    printf("\b \b");
                    fflush(stdout);
                }
                strcpy(arco_buffer, search_result);
                i = strlen(search_result);
                printf("%s", arco_buffer);
            }
                // 情况2的处理:切换到下一次输入
            else if (match_num > 1) {
                // 多匹配第2次敲tab, tap_search处理之后切换到下一次输入
                if (pre_c == '\t') {
                    // 先打印控制台前缀
                    printf("%s", g_console_perfix);
                    // 打印arco缓冲区变量
                    printf("%s", arco_buffer);
                }
            }
        }
        else {
            printf("%c", c);
            arco_buffer[i] = c;
            i++;
        }
        pre_c = c;
    }
    printf("%s\n", arco_buffer);
    strcpy(ipt_str, arco_buffer);
}

/*
 * 模拟终端输入工具区end
 */

// 测试用main函数
int main()
{
    arco_terminal_add_search_str("asdfasdf");
    arco_terminal_add_search_str("qwerqwe");
    arco_terminal_add_search_str("str1234567");
    arco_terminal_add_search_str("str1234");
    arco_terminal_add_search_str("str1233");
    arco_terminal_add_search_str("str1232");
    arco_terminal_add_search_str("arcodesuki");

    arco_terminal_set_input_perfix(">> ");

    char ipt_str[16];
    memset(ipt_str, '\0', sizeof(ipt_str));
    while (1) {
        arco_terminal_input(ipt_str);
        printf("get input str:%s\n", ipt_str);
    }

    return 0;
}

小结

首先更改终端属性,使得键入一个字符就立即响应,然后判断输入的字符类型进行处理,分别封装对backspace和tap键的处理,其中tap键的搜索要首先指定搜索范围,然后通过链表进行查找

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值