首页 > 解决方案 > (C) 当终止值为 [^\n] 时 scanf 不工作

问题描述

我正在创建一个程序,您可以在其中通过该程序执行终端命令。我想访问一个目录(使用 cd /Users/user/Desktop),但由于 scanf 在空格处终止,我被迫将终止值更改为[^\n]. 这时候错误就出现了。每当我输入命令时,它都会执行所述命令,但是下次程序通过(无限)循环时,它不会停止执行 scanf 函数之前的行。当终止值为 时,这还没有发生%s。这是程序的代码:

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

void execute(){
    char* command = (char *) malloc(15);
    char* output = (char *) malloc(4096);
    printf(">> ");
    scanf("%[^\n]", command);            //Here is the scanf function
    FILE* cmd = popen(command, "r");
    fread(output, sizeof(output), 32000, cmd);
    if (strlen(output) != 0){
        printf("\n%s\n", output);
    }
    free(output);
    pclose(cmd);
}

int main(){
    while (1){
        execute();
    }
}

这是终止值为 时的输出[^\n]

>> ls

Applications
Desktop
Documents
//Here the rest of the contents in my user folder appear (twice for some reason, that's also an issue related to this).

>> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> 
>> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> 
>> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> 
>> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> >> 
// This then goes on forever

这是终止值为时的输出%s

>> ls

Applications
Desktop
Documents
//Here the rest of the contents in my user folder appear (once)

>> //Here I can input things again

有人可以告诉我如何解决这个问题吗?(我试过了gets(),结果一样)

标签: cscanf

解决方案


由于您告诉scanf不要读取\n,它会将其留在标准输入中,因此当循环重复时,它仍然存在并导致下一次迭代立即返回一个空字符串。要修复它,有几个选择:

  • 更改"%[^\n]"" %[^\n]"忽略前导空格。
  • 添加getchar();afterscanf("%[^\n]", command);以使用换行符。
  • 使用fgets或 以外的其他阅读功能scanf。(不gets;它不能安全使用!)

您也没有检查是否scanf返回任何内容,因此如果它根本无法解析任何内容,那么您将未初始化的内存传递给popen.

旁注:您的程序中还有很多其他错误,但它们与您的直接问题无关:

  1. 您通过循环泄漏了command每次的内存。要修复它,请在函数结束之前和之后free(command);的某个位置添加。popen
  2. 您没有将最大字段宽度传递给scanf,因此如果输入的字符数超过 14 个,则会出现缓冲区溢出和损坏的内存。要修复它,请更改%[^\n]%14[^\n](14 而不是 15,以便为空终止符留出空间)。您还应该检测部分读取的情况并正确处理,以避免echo Their alarm is set删除名为isandset的文件。
  3. sizeof(output)将是指针的大小,而不是您分配的它指向的内存的大小,并且32000似乎是凭空出现的。更改为sizeof(output)1 和。320004096
  4. fread不会终止其输出,因此打印它%s会在其后打印未初始化的内存。要修复它,要么使用获取输出以外fread的其他东西,要么使用它的返回值并确保只打印那么多字符。

使用您的“固定”版本,还有一些问题:

  • 你的第一个fgets现在不会缓冲溢出,但过长的字符串仍然会导致问题。特别是,如果用户输入的命令太长,它会表现得好像他们在中间按了 Enter,导致运行两个部分命令。
  • 在子流程中执行cd不会影响父流程或任何其他子流程。要使其工作,您需要检查他们输入的命令是否以 开头cd,如果是,则直接调用chdir而不是执行popen.

这是一种正确的方法:

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

int main(){
    char *command = NULL;
    size_t commandsize = 0;
    char *output = (char *)malloc(5000);
    while(1){
        printf(">> ");
        ssize_t commandlen = getline(&command, &commandsize, stdin);
        if(commandlen < 0) {
            // assume EOF. Small chance that it was an error though
            break;
        }
        printf("%s", command);
        if(!strcmp(command, "exit\n")) {
            break;
        }
        if(!strncmp(command, "cd ", 3)) {
            command[commandlen - 1] = '\0'; // remove the newline
            if(chdir(command + 3)) {
                perror("chdir");
            }
            continue;
        }
        FILE *cmd = popen(command, "r");
        if(!cmd) {
            perror("popen");
            continue;
        }
        size_t sz;
        while ((sz = fread(output, 1, 5000, cmd)) > 0){
            fwrite(output, 1, sz, stdout);
        }
        pclose(cmd);
    }
    free(command);
    free(output);
    return 0;
}

关于它的几点说明:

  • 我没有每次通过循环都使用mallocand ,而是将这些变量移到函数范围,因此它只在程序运行的整个过程中发生一次。free
  • 我习惯getline阅读整行,并自动分配必要的空间。这和 一样popen,不是标准 C 的一部分,而是 POSIX 的一部分。
  • 我使用freadandfwrite从我们开始的流程中传输数据,以避免不得不考虑空终止符。我假设您最终会对这些数据进行某种处理;如果没有,请考虑使用system而不是popen,它会自动将输出写回给用户。

推荐阅读