个人头像 乌鸦之家

Windows下fgets对于CTRL+Z输入的响应

创建
更新
杂谈
编程杂谈

Windows下CTRL+Z在标准输入中的作用

程序读入文件需要用一个“文件结束符”(通常记作EOF,即“End of File”)来标记文件的结尾,标准输入作为一个特殊的文件,同样也有它的EOF,它就是ctrl + z

在终端按下ctrl键,然后再按下z键即可以输入EOF,在终端会显示^Z,但它实际上和手动输入的^加上Z并不相同,前者为1个字符,后者为两个字符。

但在使用C标准库的fgets读入一行命令行输入时,却发现ctrl + z并不总是会解释为EOF

在键入ctrl + z之前如果没有其它字符,才会被解释为EOF,若ctrl + z之前有其它字符,fgets根本不会结束,反而会继续等待用户输入。

循环读入用户输入的例子

char buf[64];

printf(">> ");
while(fgets(buf, sizeof(buf), stdin)){
    printf("你输入的是:");
    puts(buf);
    printf(">> ");
}

if( feof(stdin)){
    printf("文件到达末尾");
}

在这个例子中,每次用户输入之前程序会输出提示符">> "提示用户输入内容。

如果输入常规的字符,fgets会立即结束,并输出读入的字符串。

>> jkl
你输入的是:jkl

>> 

只输入ctrl + zfgets会结束,并返回NULL,使得循环结束。

>> ^Z
文件到达末尾

但若先输入其它字符,再输入ctrl + zfgets不会返回,而会继续等待用户输入。

>> jkl^Z

如果继续接入普通字符,并按下回车,会获得下面输出:

>> jkl^Z
jkl
你输入的是:jkl␦jkl

>> 

ctrl + z就对应那个镜像的问号“␦”。

为了看看这其中到底发生了啥,还是需要直接查看字符的ascii值。

观察不同情况下ctrl + z对应字符的ascii值

void print_buf_as_hex(char* p){
    printf("\n");
    while( *p ){
        printf("%x ",*p++);
    }
    printf("\n");
}

int main(){
    char buf[64];

    printf(">> ");
    while(fgets(buf, sizeof(buf), stdin)){
        printf("你输入的是:");
        print_buf_as_hex(buf);
        printf(">> ");
    }

    if( feof(stdin)){
        printf("文件到达末尾");
    }
    return 0;
}

首先是普通的输入:

>> jkl
你输入的是:
6a 6b 6c a
>>

6a 6b 6c对应jkl的ASCII值,后面的a对应换行符\n

单独的ctrl + z

>> ^Z
文件到达末尾

这种情况循环直接被跳过了,所以没有输入ascii值(不过非要看也是可以的,只不过EOF并不会被fgets读到buf中,所以不看也罢)。

先输入“jkl”再输入ctrl + z

>> jkl^Z
jkl
你输入的是:
6a 6b 6c 1a 6a 6b 6c a
>>

两处6a 6b 6c对应两处jkl,但它们中间却是个十六进制的1a

去查一下ASCII表,十六进制1a对应的是控制符sub(substitute),咱也不知道是啥作用。

引用chatgpt:
ASCII 码中十六进制为 1A 的值表示 Substitute,通常缩写为 SUB。这个字符在 ASCII 控制字符中具有特定的含义:
- ASCII 控制字符:1A 是 ASCII 表中的一个控制字符,通常用于表示替换字符或作为文件传输中的特殊标记。
- 功能:在文本处理中,SUB 通常用于表示不可打印字符或用于特殊用途,例如在早期的计算机和通信设备中,可能用于指示数据流的中断或结束。
总之,ASCII 十六进制为 1A 的值 SUB 表示替换字符,其具体的使用取决于特定的应用和上下文。

在一个网友的博客下找到了答案:

windows下的ctrl+z用来作为结束输入输出流的标值,控制台读入字符^Z,只有另起一行放在行首才会起作用,否则表示结束本行……

常见的解释性语言对此问题的处理

结论已经有了,ctrl + z只有放在行首时才表示EOF,于是我想到了各大解释性语言对这个问题的处理,像python、lua甚至是java的交互式命令行工具jshell。

python

实验结果发现python非常的智能:

>>> jkl^Z
  File "<stdin>", line 1
    jkl␦
       ^
SyntaxError: invalid non-printable character U+001A
>>>

读到ctrl + z之后直接就报错了。

lua

相比之下,lua就逊色一些,表现和我们的实验一模一样,似乎它的交互式命令行就是用fgets写的:

> jkl^Z

(这是在第二行等待输入)

继续输入之后:

> jkl^Z
jkl
stdin:1: syntax error near '<\26>'
> 

jshell

jshell就很霸气,直接禁用了ctrl + z(根本无法输入这个字符)。

然后我就在思考jshell用什么退出呢,试了一下ctrl + d(Linux的EOF),果不其然马上退出了。

但如果先输入字符再输入ctrl + d就没用了,输入任意字符之后ctrl + d会被禁用。



0

评论