【问题标题】:Forcing a terminal not to print Ctrl hotkeys when signals are caught捕获信号时强制终端不打印 Ctrl 热键
【发布时间】:2021-10-08 09:43:46
【问题描述】:

早安,

我正在为我的学校用 C 语言编写自己的 shell,它必须尽可能地类似于 bash

我必须像bash 那样处理诸如Ctrl-\ 和Ctrl-C 之类的信号;出于这个原因,我被允许使用signal 函数。它工作正常,但问题是每当捕获到 Ctrl-C 信号时(从第二个捕获开始),就会打印 ^C

在网上,我发现了一种解决方法,建议在捕获 Ctrl-C 时打印"\b \b\b \b\nminishell$ ",这将吞噬这两个符号。问题是,因为第一次没有打印^C,所以打印吞噬了我提示的两个符号,使它只是minishell而不是minishell$ ,光标显示不正确。

现在我想出了另一种解决方法,即声明一个静态布尔值以在第一次调用时不打印 baskspaces。这在 Ctrl-\ 的情况下无济于事;当我尝试写入必须替换 ^\ 的两个空格时,Ctrl-\ 继续将光标向右移动。

我不喜欢这些变通方法,想知道是否有办法指示终端不输出这些东西?我被允许使用tgetenttgetflagtgetnumtgetstrtgototputstcsetattrtcgetattr,已经阅读了他们的手册页,但似乎没有任何帮助。

【问题讨论】:

  • 如今,传统观点认为应该使用sigaction() 而不是signal() 来安装信号处理程序。
  • 不幸的是,在信号处理功能之外,只允许使用signal()。我不能使用sigaction()——这会导致项目作弊和失败。
  • 来自命令行:stty -ctlecho。在 tcsetattr() 中,该标志被命名为 ECHOCTL。此标志控制终端回显空格以下字符(NUL、TAB、CR、LF 除外,它们仍然很特殊)的方式。
  • 您确定您的处理程序正在第二次及以后被调用吗?
  • 另外,您不会在信号处理程序中调用printf(),是吗? [A] signal handler cannot, in general, call standard library functions. POSIX 将其扩展为允许您使用 safely call async-signal-safe functions from within a signal handler

标签: c terminal signals


【解决方案1】:

当您在终端上键入一个键时,会发生两件事

  • 字符在此终端上回显(显示)
  • 字符被发送(通过线路)到附加的程序

这两个动作都可以通过 termios/tcsetattr() 进行控制:可以发送或回显不同的字符,可以抑制某些字符,等等。(这些动作中的一些/大部分发生在终端中-driver ,但这与这里无关)

演示:使用tcsetattr()控制终端的回显:


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

#define _SVID_SOURCE 1
#include <termios.h>
#include <unistd.h>
#include <signal.h>

struct termios termios_save;

void reset_the_terminal(void)
{
tcsetattr(0, 0, &termios_save );
}

sig_atomic_t the_flag = 0;
void handle_the_stuff(int num)
{
char buff[4];
buff[0] = '[';
buff[2] = '0' + num%10;
num /= 10;
buff[1] = '0' + num%10;
buff[3] = ']';
write(0, buff, sizeof buff);
the_flag = 1;
}

int main (void)
{
int rc;
int ch;
struct termios termios_new;

rc = tcgetattr(0, &termios_save );
if (rc) {perror("tcgetattr"); exit(1); }

rc = atexit(reset_the_terminal);
if (rc) {perror("atexit"); exit(1); }

termios_new = termios_save;
termios_new.c_lflag &= ~ECHOCTL;
rc = tcsetattr(0, 0, &termios_new );
if (rc) {perror("tcsetattr"); exit(1); }

signal(SIGINT, handle_the_stuff);

printf("(pseudoshell)Start typing:\n" );
while(1) {
        ch = getc(stdin);
        if (the_flag) {
                printf("Saw the signal, last character was %02x\n", (unsigned) ch);
                break;
                }
        }

exit (0);
}

【讨论】:

  • 谢谢,我终于明白tcsetattr()是干什么用的了。但我还有另一个问题:在手册页上,它声明:“(不在 POSIX 中)”;但是,学校的计算机运行的是 macOS。我打印了得到的标记,清除了ECHOCTL 位,再次打印它并且数字保持不变:(然后我应该寻求解决方法吗?
  • 查看手册页,这是_SVID_SOURCE 的用途。但是:如果您的系统/ttydriver 不支持它,您将失去...
  • 不幸的是,#ifdef _SVID_SOURCE 失败了。我可以自己定义它还是尝试安装一些软件包?我接受你的回答,因为它可以在 Linux 上运行
  • 您需要自己定义_SVID_SOURCE(或_BSD_SOURCE)。但是:如果您的终端驱动程序不支持 ECHOCTL,您可以退回到原始模式(请参阅另一个答案)顺便说一句:“MacOS”是 BSD 盗版。
  • 美好的一天,再次感谢您,并告诉我该功能确实可以在我学校的计算机上运行:我运行了您的代码并且终端没有输出^C。但是,由于某种原因,它在我的程序的main() 中不起作用。它只有在我将它放在信号处理程序中时才有效,出于某种原因......为什么?此外,当我在 shell 中运行 stty 时,它仍然在 lflags 中显示 echoctl。它会自行重置。
【解决方案2】:

设置控制台这样一个软件可能会拦截所有键入的字符的方法是将终端设置为 RAW 模式。这种方式可能出现的问题是所有不在 ASCII 0-255 空间中的键,例如 èìà 将从控制台作为字节序列接收,并且所有函数和包含光标和退格的控制键不会完成任何动作,一些代码如CRLF和一些ANSI序列可能会在从输入通道读取并在输出通道上重写时完成动作。

要将终端设置为原始模式,您必须使用函数cfmakeraw,然后使用函数tcsetattr

下面的代码实现了一个简单但不是很好实现的终端,无论如何我认为这段代码是一个很好的起点。无论如何,代码流和错误控制至少必须安排得更好。

当键入一个键时,代码会写入所有进入控制台的 ASCII 字符序列。所有值小于 32 或大于 126 的字符都将写入[HEX-CODE]

I.E.在控制台点击Esc会写成[1B]Ctrl+C的代码会写成[03], F1 将是[1B]OPF11 将是[1B][23~Enter 将是[0D]

如果您按下 Ctrl+X[18] 将被写入并且程序停止,但正如您在代码中看到的那样,此行为受 SW 控制。

代码如下:

#include <stdio.h>      // Standard input/output definitions
#include <string.h>     // String function definitions
#include <unistd.h>     // UNIX standard function definitions
#include <fcntl.h>      // File control definitions
#include <errno.h>      // Error number definitions
#include <termios.h>    // POSIX terminal control definitions (struct termios)

#include <sys/ioctl.h> // Used for TCGETS2, which is required for custom baud rates
#include <sys/select.h> // might be used to manage select

int setAttr(int ch, int resetToOld);

#define IN 0
#define OUT 1

typedef struct TermCap
{
    int fd;
    struct termios oldTermios;
    struct termios newTermios;
    // fd_set fds; // might be used to manage select
} TermCap;

TermCap m_termCap[2];

int main()
{
    int i,ex=0;
    char msg;
    char buff[20];

    m_termCap[IN].fd=STDIN_FILENO;
    m_termCap[OUT].fd=STDOUT_FILENO;

    // Gets STDIN config and set raw config
    setAttr(IN,0);

    // Gets STDOUT config and set raw config
    setAttr(OUT,0);

    // Console loop ... the console terminates when ^X is intercepted.
    do {
        do {
            i=read(m_termCap[IN].fd,&msg,1);
            if (i>0){
                if (msg<32 || msg>126) {
                    sprintf(buff,"[%02X]",(unsigned char)msg);
                    write(m_termCap[OUT].fd,buff,4);
                    if (msg==24)
                        ex=1;
                }else{
                    write(m_termCap[OUT].fd,&msg,i);
                }
            }
            usleep(10000); // a minimal delay of 10 millisec
        } while(i>0 && !ex);
    } while(!ex);

    // Reset console to initial state.
    setAttr(IN,1);
    setAttr(OUT,1);

    printf("\r\n\nThe end!");
    return 0;
}

int setAttr(int ch, int resetToOld)
{
    int retVal=0;
    int i;

    if (!resetToOld) {
        // Read old term config
        i=tcgetattr(m_termCap[ch].fd, &m_termCap[ch].oldTermios);
        if (i==-1) {
            return 1;
        }
    }

    m_termCap[ch].newTermios = m_termCap[ch].oldTermios;

    if (!resetToOld) {
        // Terminal in raw mode
        cfmakeraw(&m_termCap[ch].newTermios);
    }

    i=tcsetattr(m_termCap[ch].fd, TCSANOW, &m_termCap[ch].newTermios);

    if (i==-1) {
        retVal = 2;
    }

    return retVal;
}
 

【讨论】:

    【解决方案3】:

    这不行吗?

    void signalHandler(int signo){
     if(signo==SIGINT){
      printf("\b\b  \b\b");
      fflush(NULL);
      printf("\nHello World\n");    
     }
    }
    

    在我的 shell 中,它似乎工作正常。第一个 printf 和 fflush 是您必须在处理程序中实现的。之后的 printf 只是我向您展示您可以在 ^C 不出现之后做任何您想做的事情的一种方式。

    为什么它不会出现?在第一个 printf 中,我使用退格和空格擦除字符。由于标准输出默认是缓冲的,我不想使用换行符,所以我手动刷新了缓冲区。

    【讨论】:

    • 您不能在信号处理程序中使用 printf() 和朋友。
    猜你喜欢
    • 2020-06-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-10-17
    • 1970-01-01
    • 2023-01-29
    • 1970-01-01
    相关资源
    最近更新 更多