【问题标题】:fprintf not writing on ^C but is writing on exitfprintf 不在 ^C 上写入,但在退出时写入
【发布时间】:2018-11-01 21:58:27
【问题描述】:

我正在用 C 语言编写一种终端 shell 语言,称为 tsh(tech shell)。 shell 必须能够处理输出重定向 (">")。我正在使用 fprintf 和 fopen 来完成此任务。 该问题类似于以下内容:

如果我在 shell 中通过 exit 命令使用重定向:

tsh$ pwd > out.txt
tsh$ exit

bash$ cat out.txt
/tmp
bash$

但是如果我用 ctrl+c 而不是 exit 做同样的事情:

tsh$ pwd > out.txt
tsh$ ^C

bash$ cat out.txt
bash$

这意味着文件被打开,但是当我停止进程时,由于某种原因,tsh 的 pwd 的输出没有被写入。

tsh$ pwd 但是,如果没有重定向,打印到标准输出就好了。

我在 bash 中 cat 文件的原因是因为 tsh 不支持非内置命令(还),但考虑到在我退出 shell 之前不会写入内容,我不这么认为即使我可以从我的壳里 cat 也能工作。

另外,如果out.txt 尚不存在,它确实在两种情况下都会被创建,但仍然只会在第一次写入。 简而言之,我可以毫无问题地获得对文件的引用,只需写入即可。

这是 tsh.c 的当前源代码,使用 gcc 编译:

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

const char EXIT[4] = "exit";
const char PWD[3] = "pwd";
const char * DELIM = " \n\r\t";

FILE * outfile, * infile;

char cwd[1024]; //directory to print at beginning of each line

char in[1024]; //user input
char * token;

int main()
{

    while(1)
    {

        outfile = stdout;   
        infile = stdin;

        getcwd(cwd, sizeof(cwd));
        printf("%s$ ",cwd); //prompt with cwd       

        fgets(in, sizeof(in), stdin);

        char * fileref;
        if((fileref = strstr(in,">")))
        {

            fileref = strtok(++fileref, DELIM);
            outfile = fopen(fileref, "w");

        }

        token = strtok(in, DELIM);
        if (token != NULL)
        {
            if(!strncmp(token, EXIT, sizeof(EXIT)))
            {

                token = strtok(NULL, DELIM);
                if (token == NULL)
                {
                    printf("Exiting with status code 0.\n");
                    exit(0);
                }
                else if (isdigit(*token))
                {
                    int code = *token - '0';
                    printf("Exiting with status code %d\n",code);
                    exit(code);
                }
            }
            else if(!strncmp(token, PWD, sizeof(PWD)))
            {
                fprintf(outfile, "%s\n",cwd); //This should get written to the file if I provide a redirection
                continue;
            }

            fputs("\n", outfile);

        }

        fclose(outfile);

    }

}

如果进程没有干净地退出,是否有某种内置的回滚机制会阻止文件写入?是否必须拦截并覆盖 ctrl+c 信号?

我知道这里有很多问题和不良做法,因为这是我第一次接触 C,但如果建议可以限于我遇到的具体问题,我将不胜感激。

提前致谢。

【问题讨论】:

  • 可能 exit() 关闭所有打开的文件并刷新它们的流,但 ^C 中止没有流刷新。您可能希望在停止 shell 执行之前捕获 ^C 并刷新文件。当文件被缓冲时,视频输出应该是无缓冲的,这应该是没有重定向没有问题的原因。
  • 尝试在 fprintf 之后调用 fflush。
  • 我要关闭并刷新吗?
  • 与您的问题无关,但您会发现 libreadline 对您的项目有帮助:tiswww.case.edu/php/chet/readline/rltop.html

标签: c shell printf fopen io-redirection


【解决方案1】:

当您调用exit() 时,您将导致您的进程以干净的方式终止,这可以确保正确关闭所有打开的文件(因此刷新所有缓冲的输出)。

另一方面,当您按下 CTRL+C 时,您正在向您的进程发送一个 SIGINT,并且当接收到此类信号时,进程的默认行为是终止;但这不是一个干净的进程终止(它类似于遇到分段错误时发生的情况),因此任何打开的文件都不会正确关闭,如果有任何缓冲输出,它将丢失。

在您的应用程序中,您将能够通过修复代码中的逻辑错误来解决您描述的问题:在主 while() 循环内,当您在用户输入,但是当你执行“pwd”命令时,你不会关闭fopen()返回的文件流,并且在while()循环的下一次迭代中你用stdout覆盖文件流指针,有效地泄漏一个打开的文件。相反,您应该确保在开始下一次循环迭代之前关闭文件。如下:

while(1)
{

    outfile = stdout;
    infile = stdin;

    getcwd(cwd, sizeof(cwd));
    printf("%s$ ",cwd); //prompt with cwd

    fgets(in, sizeof(in), stdin);

    char * fileref;
    if((fileref = strstr(in,">")))
    {

        fileref = strtok(++fileref, DELIM);
        outfile = fopen(fileref, "w");

    }

    token = strtok(in, DELIM);
    if (token != NULL)
    {
        if(!strncmp(token, EXIT, sizeof(EXIT)))
        {

            token = strtok(NULL, DELIM);
            if (token == NULL)
            {
                printf("Exiting with status code 0.\n");
                exit(0);
            }
            else if (isdigit(*token))
            {
                int code = *token - '0';
                printf("Exiting with status code %d\n",code);
                exit(code);
            }
        }
        else if(!strncmp(token, PWD, sizeof(PWD)))
        {
            fprintf(outfile, "%s\n",cwd); //This should get written to the file if I provide a redirection
        }
        else
        {
            fputs("\n", outfile);
        }
    }
    if(fileref)
    {
        fclose(outfile);
    }
}

【讨论】:

  • 谢谢。我已经使用 open 和 dup2 重新设计了我的 shell,但我会以某种方式尝试。
【解决方案2】:

^C 正在向您的程序发送 KILL 信号 (KILL)。您应该使用 ^D 来结束输入 (EOF)。

   fgets() returns s on success, and NULL on error or when end of file occurs while no characters have been read.

所以你必须使用:while (fgets(in, sizeof(in), stdin)) {}

【讨论】:

  • 他正在编写自己的自定义外壳。这些约定可能不适用,因为他可以自行决定是否遵守这些约定。
猜你喜欢
  • 2012-03-08
  • 1970-01-01
  • 1970-01-01
  • 2015-03-27
  • 2021-10-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多