/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测试程序除了你自己,没有人会 运行。如果 whereToPrint
是 NULL
,则写另外两行调用 perror
和 exit
。老实说,即使它 是 一个只有我 运行 的 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" 失败会发生什么?
如果你的程序检查fopen
失败,它会打印一条类似"Impossible error! Can't open /dev/null"
的信息,然后就会很清楚发生了什么。
如果您的程序未能检查 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" 技巧 - 写起来很有趣,如果它们有效就很好,但请不要以牺牲代码的其他更重要的方面为代价坚持它们,比如确保它在所有情况下都能正常工作。 :-)
我想通过将目标文件重定向到 /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测试程序除了你自己,没有人会 运行。如果 whereToPrint
是 NULL
,则写另外两行调用 perror
和 exit
。老实说,即使它 是 一个只有我 运行 的 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" 失败会发生什么?
如果你的程序检查
fopen
失败,它会打印一条类似"Impossible error! Can't open /dev/null"
的信息,然后就会很清楚发生了什么。如果您的程序未能检查
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" 技巧 - 写起来很有趣,如果它们有效就很好,但请不要以牺牲代码的其他更重要的方面为代价坚持它们,比如确保它在所有情况下都能正常工作。 :-)