在C语言中如何隐藏^C按键的操作?

5
我正在创建自己的Shell,并希望在Linux发行版的任何用户按下ctrl+c时禁用^C。我不需要处理信号SIGINT,因为我已经做了,以免程序在ctrl+c上停止运行。我只是想知道如何隐藏这两个字符^C。是否有可以调用的函数或要在程序启动时设置的环境变量?
编辑
  int a = fork();
  if (!a) {
    char *cmd[] = {"/bin/stty", 0 };
    char *cmd_args[] = {" ", "-echoctl", 0};
    execve(cmd[0], cmd_args, env);
  }

尝试过这个方法。它能在按下ctrl-c时去掉^C,但仍会显示一个方块字符,似乎是EOT(003 ASCII码)。

你需要在终端上禁用回显(但这也意味着你需要自己处理输入的回显)。 - Antti Haapala -- Слава Україні
我不确定理解你的意思。 - Romain-p
我认为我可以使用execv和stty -echoctl。 - Romain-p
@Olaf 是的,有一种方法。 - Antti Haapala -- Слава Україні
使用execvp()而不是execve(),并且不提供环境变量。如果您确定需要特殊环境而不是继承的环境,请查看是否有execvpe()可用。请注意,如果在启动程序之前设置了echoctl,当您的程序被控制退出时,重置它会很有礼貌(如果程序失去控制而退出,则当然无法做到这一点)。请注意,cmd_args数组应该像这样:char *cmd_args[] = { "stty", "-echoctl", 0 }; - Jonathan Leffler
显示剩余6条评论
3个回答

5
^C来自Linux终端驱动程序的回显。以下是一个用C语言编写的示例程序。它首先禁用并保存当前设置,在程序退出时注册atexit处理程序以恢复设置,然后在标准输入的终端上禁用了回显。然后进入一个无限循环。现在,当您在终端上输入任何内容时,甚至包括那个^C,都不会显示出来。
壳程序使用的技巧是它们完全替换了终端上的输入处理,关闭了规范化的输入处理,并按照自己的方式逐个字符读取标准输入,并处理回显 - 这需要比Stack Overflow答案中可能的代码更多的代码。
#include <termios.h>
#include <unistd.h>
#include <stdlib.h>


struct termios saved;

void restore(void) {
    tcsetattr(STDIN_FILENO, TCSANOW, &saved);
}


int main() {
    struct termios attributes;

    tcgetattr(STDIN_FILENO, &saved);
    atexit(restore);

    tcgetattr(STDIN_FILENO, &attributes);
    attributes.c_lflag &= ~ ECHO;
    tcsetattr(STDIN_FILENO, TCSAFLUSH, &attributes);

    printf("Entering the loop\n");
    while(1) {};
}

好的,谢谢你的回答。但这是一个学校项目,我不能使用你目前正在使用的一些函数。你认为stty能够与execv一起使用吗? - Romain-p
我不能使用一些我正在使用的函数?!这是我听过的最奇怪的事情!那这是什么学校项目?是的,stty会起作用...并且stty将调用相同的函数。 - Antti Haapala -- Слава Україні
我知道这很愚蠢,但是除非指定某些人被授权,否则我们禁止每个函数。我有一个stty的问题,它显示一个正方形,就像不可写字符。 - Romain-p
1
如果您只能使用特定的函数,请在您的原始问题中列出所有这些函数,以帮助我们更好地为您提供帮助。 - Mark Plotnick

3
运行stty -echoctl命令可以隐藏它。更多详情请参见man stty

我应该在运行时执行它吗? - Romain-p
execve("stty", args, env); 不起作用。回答不好。 - Romain-p
“不起作用”并不是一个有用的说法。你尝试时发生了什么?另外注意,execve 不会搜索你的路径,因此你需要完全限定它或使用像 execvp 这样的包装器。 - Joseph Sible-Reinstate Monica
我应该在这之前fork()吗?即使使用/bin/stty也不起作用。 - Romain-p
是的,由于exec函数族替换当前进程而不是创建新进程。 - Joseph Sible-Reinstate Monica
@Romain-p 你必须使用带有execve的完整路径“/bin/stty”! - Zibri

0
您可以使用 ANSI 转义码来删除 ^C 字符。 在处理 SIGINT 信号的函数中,打印将光标向左移动两次的字符,然后删除右侧直到行尾的所有字符。
以下示例在 macOS 和 Raspberry Pi OS 上有效。
在 Windows 上不需要这个技巧,因为按下 ctrl+c 是静默的。
/**
    remove_ctrl_c.c
    gcc remove_ctrl_c.c -o remove_ctrl_c && ./remove_ctrl_c

    Remove the characters ^C with ANSI escape codes.
    (see https://en.wikipedia.org/wiki/ANSI_escape_code)

    \b     : Move cursor one character on the left.
    \b     : Idem
    \033[K : Delete all the characters on the right of
             the cursor until the end of the line.
             You can also use two spaces if you prefer,
             but they will be present in the output although
             they are not visible.
    \n     : Add a new line. This is optional,
             but if you remove it and some characters
             are printed on the last line, the terminal
             will add an extra % character to indicate
             that the new line character was absent.

    Note that, as the printf command is buffered,
    we need to use the fflush command before the end
    of the program to force stdout to be updated.
*/

#include <stdio.h>
#include <stdlib.h>
#include <signal.h>

volatile sig_atomic_t KEYBOARD_INTERRUPT = 0;

void handleSignal(int signal)
{
    KEYBOARD_INTERRUPT = 1;
}

int main()
{
    signal(SIGINT, handleSignal);
    printf("Remove ^C on exit!");
    fflush(stdout);
    while (!KEYBOARD_INTERRUPT)
    {
    }
    printf("\b\b\033[K\n");
    fflush(stdout);
    return 0;
}

一个信号处理程序居然调用了printf()fflush()exit()?!?!?这三个函数都不是异步信号安全的? - Andrew Henle
@AndrewHenle,感谢您的评论。我已经相应地修改了示例。 - nico

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接