/dev/null 总是可以打开吗?

Is /dev/null always openable?

我想通过将目标文件重定向到 /dev/null 来抑制某些 fprintf 调用。但是我可以确定 fopen("/dev/null", "w"); 永远不会 returns NULL。也就是说,是不是每次都可以打开这个"file"?

如果是这样,我可以使用这个不错的三元运算符:

FILE *whereToPrint = (strcmp(custom_topic, ERROR_TOPIC) == 0) ? fopen("/dev/null", "w") : stdout;

fprintf(whereToPrint, "Message sent!\n\n");

是的,在正常运行的系统上,/dev/null 是全局可写的:

 ls -l /dev/null
 crw-rw-rw- 1 root root 1, 3 Jul 20  2017 /dev/null

所以它会一直有效。这不是抑制输出的最有效方法。如果你不想写,最好不要尝试写。但如果输出不是很多,那也没关系。

有人指出root可以设置/dev/null的权限,使other不可写。或者他们可以完全删除该设备。这是真的.. 但它会导致 unix 损坏。 /dev/null 应该具有我上面显示的权限。它是这样安装的,永远不应更改。尽管如此,您应该在打开任何文件时检查 fopen()open() 的 return 值。

是的,fopen 可能由于某些原因无法打开 /dev/null,例如/dev/null 的权限问题。虽然很少见,但不能否认这种可能性。

可能/dev/null未写table或丢失。我的意思是,您可以打开一个根 shell 并键入 rm /dev/null 并按 RETURN 它会很高兴地继续并删除设备节点。

如果 /dev/null 未写入 table 或丢失,您的程序失败是合理的。许多其他程序都有 属性。但是你的程序盲目地假设fopen("/dev/null", "w")成功是合理的,除非它是one-off测试程序除了你自己,没有人会 运行。如果 whereToPrintNULL,则写另外两行调用 perrorexit。老实说,即使它 一个只有我 运行 的 one-off 测试程序,我也会包括在内。打字的额外时间不超过十秒,谁知道呢?也许我遇到的问题是某些有问题的系统脚本在我背后被删除了 /dev/null!

编辑: 我刚刚想到您可能会犹豫是否要检查 fopen 的结果,这不是因为额外的输入,而是因为您没有想搞砸你的 "nice ternary expression"。没有必要把它搞砸;您只需在之后立即支票:

FILE *whereToPrint = (strcmp(custom_topic, ERROR_TOPIC) == 0) ? fopen("/dev/null", "w") : stdout;
if (!whereToPrint) {
    perror("/dev/null");
    exit(1);
}

如果 whereToPrint 已设置为等于 stdout,则它不会是 NULL,并且检查会成功。

如果你有一大堆 bunch 这些错误主题,那么你应该把它们和它们的名字放在一个 table 中,这样你就可以在初始化时循环它们, 而且也不需要多次打开 /dev/null:

enum error_topic_codes { 
    ET_INPUT, ET_CRUNCHING, ET_FROBNICATING, ET_OUTPUT,
    ET_MISC
};
struct error_report_s {
    const char *label;
    FILE *fp;
};
static struct error_report_s error_destinations[] = {
    /* ET_INPUT */        { "input", 0 },
    /* ET_CRUNCHING */    { "crunching", 0 },
    /* ET_FROBNICATING */ { "frobnicating", 0 },
    /* ET_OUTPUT */       { "output", 0 },
    /* ET_MISC */         { "misc", 0 },
};

void error_report_init (const char *squelched)
{
    FILE *devnull = fopen("/dev/null", "w");
    if (!devnull) {
        perror("/dev/null");
        exit(1);
    }
    for (int i = 0; i <= ET_MISC; i++)
        error_destinations[i].fp =
            strstr(squelched, error_destinations[i].label)
            ? devnull : stderr;
    /* FIXME 2018-02-23: This leaks a FILE if none of the
       error categories are squelched.  */
}

是的,/dev/null 始终可以打开 -- 除非它不能打开。

这听起来很傻,但我不是在开玩笑。如果 /dev/null 无法打开,您的系统可能已严重损坏,可能处于无法正常运行的边缘——但知道这并不等同于保证文件可以打开。

打开文件失败总是原因。您永远不应该找借口不检查 fopen 的 return 值是否失败。

你知道的可能永远不会发生,它可能永远不会发生在 properly-functioning 系统上,但问问你自己,如果打开 /dev/null "impossibly" 失败会发生什么?

  1. 如果你的程序检查fopen失败,它会打印一条类似"Impossible error! Can't open /dev/null"的信息,然后就会很清楚发生了什么。

  2. 如果您的程序未能检查 fopen 故障,它会在第一次尝试将内容打印到 whereToPrint 时莫名其妙地崩溃,您的用户会感到疑惑出了什么问题。

神秘崩溃的程序是糟糕的。能告诉您发生了什么的程序是好的。

而且,您可以告诉用户发生的事情越多越好。我建议打印 "Impossible error! Can't open /dev/null",这总比没有好,但实际上仍然很不完整。你真的应该编写这样的代码:

#include <stdio,h>
#include <string.h>
#include <errno.h>

FILE *whereToPrint;

if(strcmp(custom_topic, ERROR_TOPIC) != 0)
    whereToPrint  = stdout;
else if((whereToPrint = fopen("/dev/null", "w")) == NULL) {
    fprintf(stderr, "Impossible error! Can't open /dev/null: %s\n", strerror(errno));
    exit(1);
}

现在,在那个 "impossible" 失败的时候,它会告诉你 为什么 它无法打开 /dev/null,这可能非常棒有用的信息。它可能打印

Impossible error! Can't open /dev/null: No such file or directory

if /dev/null 不知何故不存在。或者它可能打印

Impossible error! Can't open /dev/null: Permission denied

如果像其他人所建议的那样,有人错误地限制了您系统上 /dev/null 的权限。或者它可能打印

Impossible error! Can't open /dev/null: Too many open files

事实上,由于您的程序中存在错误,即使在 correctly-configured 系统上它也可能会失败!

例如,回到你的"nice ternary operator",如果你曾经写过类似

的东西
void log_message(const char *msg)
{
    FILE *whereToPrint = (strcmp(custom_topic, ERROR_TOPIC) == 0) ?
                                       fopen("/dev/null", "w") : stdout;
    fprintf(whereToPrint, "%s", msg);
}

你很可能迟早会遇到 "Too many open files" 错误,因为我在这里编写的 log_message() 函数当然有一个错误:它打开文件(可能)调用它的时间,但永远不会关闭它。

"Nice" 使用三元运算符 - 或任何其他 "nice" 技巧 - 写起来很有趣,如果它们有效就很好,但请不要以牺牲代码的其他更重要的方面为代价坚持它们,比如确保它在所有情况下都能正常工作。 :-)