无法从管道读取,以及另一个标准输入问题

Can not read from a pipe, and another stdin issue

所以,我刚才在这里问过,但那个问题的一半只是我愚蠢。我还有问题。我希望这会比之前的问题更清楚。

我正在写 POSIX cat,我几乎可以正常工作了,但我有几个问题:

  1. 我的 cat 无法从管道读取,我真的不知道为什么(重定向 (<) 工作正常)

  2. 我不知道如何让它连续读取标准输入,而不会出现一些问题。我有一个工作“正常”的版本,但会造成堆栈溢出。如果只有 stdin,另一个版本不会停止从 stdin 读取,即:my-cat < file 会从 stdin 读取直到它被终止,这是不应该的,但是如果没有文件,它必须从 stdin 读取并等待终止已供应。

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/stat.h>
#include <fcntl.h>

int main(int argc, char *argv[])
{
    char opt;
    while ((opt = getopt(argc, argv, "u")) != EOF) {
        switch(opt) {
            case 'u':
                /* Make the output un-buffered */
                setbuf(stdout, NULL);
                break;
            default:
                break;
        }
    }

    argc -= optind;
    argv += optind;

    int i = 0, fildes, fs = 0;

    do {
        /* Check for operands, if none or operand = "-". Read from stdin */
        if (argc == 0 || !strcmp(argv[i], "-")) {
            fildes = STDIN_FILENO;
        } else {
            fildes = open(argv[i], O_RDONLY);
        }

        /* Check for directories */
        struct stat fb;
        if (!fstat(fildes, &fb) && S_ISDIR(fb.st_mode)) {
            fprintf(stderr, "pcat: %s: Is a directory\n", argv[i]);
            i++;
            continue;
        }

        /* Get file size */
        fs = fb.st_size;

        /* If bytes are read, write them to stdout */
        char *buf = malloc(fs * sizeof(char));
        while ((read(fildes, buf, fs)) > 0)
            write(STDOUT_FILENO, buf, fs);

        free(buf);

        /* Close file if it's not stdin */
        if (fildes != STDIN_FILENO)
            close(fildes);

        i++;
    } while (i < argc);

    return 0;
}

管道没有尺寸,终端也没有。 st_size 字段的内容对于此类文件是未定义的。 (在我的系统上它似乎总是包含 0,但我认为没有任何跨平台保证。)

因此,您一次性读取整个文件并再次全部写入的计划对于非常规文件是行不通的,而且即使对于它们也是有风险的(不保证读取 return请求的全部字节数)。如果文件很大,它也是一个不必要的内存消耗。

更好的策略是读入一个固定大小的缓冲区,并且只写出您成功读取的字节数。重复此操作直到到达文件末尾,由 read() returning 0 指示。这就是您解决第二个问题的方法。

类似地,write() 不能保证写出您要求的全部字节数,因此您需要检查它的 return 值,如果它很短,再次尝试写出剩余的字节。

这是一个例子:

#define BUFSIZE 65536  // arbitrary choice, can be tuned for performance

ssize_t nread;
char buf[BUFSIZE]; // or char *buf = malloc(BUFSIZE);
while ((nread = read(filedes, buf, BUFSIZE)) > 0) {
    ssize_t written = 0;
    while (written < nread) {
        ssize_t ret = write(STDOUT_FILENO, buf + written, nread - written);
        if (ret <= 0)
            // handle error
        written += ret;
    }
}
if (nread < 0)
    // handle error

作为最后的评论,您的程序总体上缺乏错误检查;例如如果无法打开文件,它将继续 filedes == -1。重要的是检查您发出的每个系统调用的 return 值,并相应地处理错误。这对于在现实生活中使用的程序来说是必不可少的,甚至对于作为练习创建的玩具程序来说,这对于调试它们也是非常有帮助的。 (例如,错误检查可能会为您提供一些线索,帮助您找出该程序的问题所在。)

你的cat(你可以叫它my-cat,但我更喜欢叫它felix,请允许我说双关语)应该和stdio一起使用是时候从 stdio 包的缓冲中获益了。下面是 cat 的简化版本,只使用 stdio 包(几乎与 K&R 中显示的完全相同),你会看到它完全有效,如图所示(你会看到结构几乎完全一样和你一样,但我简化了数据副本的处理/像 K&R 书/和参数的处理/你的有点网状/):

felix.c

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

#define ERR(_code, _fmt, ...) do {            \
        fprintf(stderr,"%s: " _fmt, progname, \
                ##__VA_ARGS__);               \
        if (_code) exit(_code);               \
    } while (0)

char *progname = "cat";

void process(FILE *f);

int main(int argc, char **argv)
{
    int opt;
    while ((opt = getopt(argc, argv, "u")) != EOF) {
        switch (opt) {
        case 'u': setbuf(stdout, NULL); break;
        }
    }

    /* for the case it has been renamed, calculate the basename
     * of argv[0] (progname is used in the macro ERR above) */
    progname = strrchr(argv[0], '/');
    progname = progname
             ? progname + 1
             : argv[0];

    /* shift options */
    argc -= optind;
    argv += optind;

    if (argc) {
        int i;
        for (i = 0; i < argc; i++) {
            FILE *f = fopen(argv[i], "r");
            if (!f) {
                ERR(EXIT_FAILURE,
                    "%s: %s (errno = %d)\n",
                    argv[i], strerror(errno), errno);
            }
            process(f);
            fclose(f);
        }
    } else {
        process(stdin);
    }
    exit(EXIT_SUCCESS);
}

/* you don't need to complicate here, fgetc and putchar use buffering as you stated in main
 * (no output buffering if you do the setbuf(NULL) and input buffering all the time).  The buffer
 * size is best to leave stdio to calculate it, as it queries the filesystem to get the best
 * input/output size and create buffers this size. and the processing is simple with a loop like
 * the one below. You'll get no appreciable difference between this and any other input/output.
 * you can believe me, I've tested it. */
void process(FILE *f)
{
    int c;
    while ((c = fgetc(f)) != EOF) {
        putchar(c);
    }
}

如您所见,没有专门做任何事情来支持重定向,因为重定向不是在程序内部完成的,而是由调用它的程序完成的(在本例中为 shell) 当您开始时一个程序,你会收到三个已经打开的文件描述符。这些是 shell 正在使用的那些,或者是 shell 在启动程序之前刚刚放在 0、1 和 2 位置的那些。所以你的程序与处理重定向无关。一切都在 shell... 中完成(在这种情况下),这就是为什么你的程序重定向有效,即使你没有做任何事情让它工作。如果您要调用一个程序并将其输入、输出或标准错误重定向到某个地方(而这个地方不是您从父进程收到的标准输入、输出或错误),您只需要进行重定向...但是这个我的猫不是这种情况。