如何将UNIX中的错误号(errno)转换为相应的字符串?

46

在 UNIX 中是否有将 errno 转换为相应字符串的函数,例如将 EIDRM 转换为 "EIDRM"。使用整数 errnos 进行错误调试非常烦人。

UNIX中有一个函数 strerror()可以将errno转化为对应的字符串形式。例如,strerror(EIDRM)将返回"EIDRM"。
6个回答

79

strerror() 应该就可以了。 http://linux.die.net/man/3/strerror

请注意,为了便于查找这些内容,您可以自己找到:如果您键入 man errno(或其他要调查的函数),并查看手册页面的底部,您将看到相关函数列表。如果您依次使用 man 查看它们(根据名称猜测哪些先进行),通常可以找到类似问题的答案。


1
strerror将会把它们转换为比仅有的“EIDRM”更有用的字符串,但这也是我会给出的答案。 - Paul Tomblin
4
那些字符串实际上并不是很有用,因为系统调用的手册通常会用枚举代码来指定错误行为。该字符串最多只是一个提示。 - orm
2
危险,威尔·罗宾逊! strerror() 的字符串是本地化的。当我检查一个错误字符串时,发现我的用户平台返回的是“Arquivo ou diretório não encontrado”,而不是“没有这样的文件或目录”。(这是在一个errno值本身没有暴露的语言中。) - David Given
@orm 是的,对于初学者来说,在常见情况下这些字符串非常有用,但是在一定程度上,这些提示可能会变得无用甚至误导。 - mtraceur
@davidgiven 听起来你遇到了我写errnoname库的原因:必须以编程方式解析错误字符串,因为这是唯一提供错误信息的形式。对我来说,最大的问题在于像rmmv和iproute2命令这样的工具,它们都缺乏其他区分例如实际上意味着幂等成功的错误和其他错误的方法。现在我们只需要说服每个人将errno名称以可解析的方式放入他们的错误消息中。 - mtraceur

14

这里有另一种解决方案,完全解决了你所遇到的问题,但它是用 Python 而不是 C 实现的:

>>> import errno
>>> errno.errorcode[errno.EIDRM]
'EIDRM'

1
这是一个很好的答案,但值得知道的是Python仅包含所有可能的errno值的子集,因此在某些系统上,它不会为所有值都有条目。对于典型情况,这不是问题,但如果您将其用作通用查找错误代码的方式,则可能会遇到此问题。 - mtraceur
谢谢!我急需这个... - pugi

14

现在已经有一个由moreutils包分发的errno工具。

$ errno -h
Usage: errno [-lsS] [--list] [--search] [--search-all-locales] [keyword]

$ errno -l
EPERM 1 Operation not permitted
ENOENT 2 No such file or directory
ESRCH 3 No such process
EINTR 4 Interrupted system call
EIO 5 Input/output error
ENXIO 6 No such device or address
E2BIG 7 Argument list too long
ENOEXEC 8 Exec format error
...

$ errno 11
EAGAIN 11 Resource temporarily unavailable

$ errno -s Ouput
EIO 5 Input/output error

4

我不确定这种enum类型的名称,但是为了调试和错误报告目的,您可以使用C函数perror(3)strerror(3),它们返回错误代码的可读表示。有关更多详细信息,请参阅man页面。


我认为perror()仅将内容打印到STDERR,而不是将数字转换为值。 我记错了吗(并且误读了手册页面)? - atk
是的,perror 是一个用于调试与 errno 相关问题的工具,并将其消息写入 STDERR - Andrey Vlasovskikh

3
如果你确实想要 EIDRM 而不是它的错误字符串:不可以。但是,在 OpenBSD 上,

man errno|egrep ' [0-9]+ E[A-Z]+'|sed 's/^ *//'|cut -d' ' -f1,2

这段代码会打印出一个漂亮的表格,其中包含“...\n89 EIDM\n...”等信息。您可以将其转换为所需编程语言的数据结构。


3

我最近写了errnoname来实现这个功能。

在UNIX中没有标准函数可以实现这个功能。(GNU的glibc有strerrorname_np,但是其他C库目前都没有提供它,因此它甚至不在所有Linux发行版上都可用,更别说其他地方了。)

strerror(还有strerror_rstrerror_lperror)打印出关于错误的“人性化”提示,但它们的输出:

  • 通常没有文档记录,
  • 没有标准化,
  • 不能保证遵循任何长度或格式的限制,
  • 通常根据语言环境设置而异,以及
  • 并不总是在所有情况下都有意义(例如,它们倾向于为EEXIST打印文件已存在,即使该错误经常返回给非文件的事物)。

这意味着它们表面上很用户友好,但实际上并不是。

  • 其他代码无法可靠地解析它们(对自动化、监控、脚本、包装器程序等更糟糕),
  • 在边界情况下会误导,
  • 妨碍有技术经验的用户。

具有讽刺意味的是,没有什么阻止这些函数在所有情况下都使用 errno 符号名称作为其错误字符串 - 特别是如果它们只针对特定语言环境执行此操作,例如特殊的C语言环境。但我所知道的libc都没有这样做。

无论如何,由于我的 errnoname 已在“零条款BSD许可证”(0BSD)下发布,这是一种宽松的许可证,或者更准确地说是等同于公共领域的许可证,所以您可以随心所欲地使用它。

为了使这个答案独立存在,同时仍然符合答案字符限制,在下面提供了 errnoname 函数的两个缩写版本。

请注意:

  1. 这涵盖了截至2020年1月初的Linux、Darwin(Mac OS X和iOS X)、FreeBSD、NetBSD、OpenBSD、DragonflyBSD以及几个闭源Unix的所有或大部分errno名称。

  2. 如果您提供一个它不知道名称的errno值,它将返回空指针。

变体1:简单、通用

这个非常便携且简单,没有需要担心的边缘情况。它可以编译成几乎任何C89或更好的编译器。 (可能甚至是C++编译器,随着语言的分化,这种情况越来越少。)

当优化程度足够高时,这个变体可以在现代编译器上编译为非常高效的代码(数组查找而不是switch语句),但具体情况可能会有所不同。

#include <errno.h>

char const * errnoname(int errno_)
{
    switch(errno_)
    {
#ifdef E2BIG
        case E2BIG: return "E2BIG";
#endif
#ifdef EACCES
        case EACCES: return "EACCES";
#endif
/*
    repeat for the other 100+ errno names,
    don't forget to handle possible duplicates
    like EAGAIN and EWOULDBLOCK
*/
    }
    return 0;
}

变体2:显式高效,适用于大多数系统

这个方法更加明显高效,并且会非常可靠地编译出高效的代码,因为它使数组查找变得明确,不依赖于编译器的优化。

只要系统具有正数、相对较小和相当连续的errno值,就可以安全使用。

只能在实现了数组指定初始化程序的乱序(C99或更高版本,目前所有C++版本均不包括在内)的编译器上编译。

#include <errno.h>

char const * errnoname(int errno_)
{
    static char const * const names[] =
    {
#ifdef E2BIG
        [E2BIG] = "E2BIG",
#endif
#ifdef EACCES
        [EACCES] = "EACCES",
#endif
/*
    repeat for the other 100+ errno names,
    don't forget to handle possible duplicates
    like EAGAIN and EWOULDBLOCK
*/
    };
    if(errno_ >= 0 && errno_ < (sizeof(names) / sizeof(*names)))
    {
        return names[errno_];
    }
    return 0;
}

有一个errnoname()函数的想法很好,但可惜它使用了switch-case。对于错误处理来说可能是不错的,但对于其他用途(如跟踪器)来说,这是不必要的次优解。你应该创建一个大数组,使用errno代码作为索引。我的意思是,使用语法{...,[index] = value,...}。当然,你也可以在那里留下空洞。 - vvaltchev
1
@vvaltchev,我现在已经添加了一个基于数组的实现选项(在编译时定义ERRNONAME_SAFE_TO_USE_ARRAY激活)到errnoname中。(在优化部分有文档记录。)感谢您提出这个问题。在重新考虑如何解决它之前,我一直在等待是否有对基于数组的方法的需求。 - mtraceur
干得好,伙计!你的项目获得了第一个GitHub星标 :-)我为我的情况实现了一种自定义解决方案(我只需要Linux errno代码),但知道你添加了这个功能仍然很好。无论如何,是的,我理解理论上某些系统可能存在大的errno代码的担忧,但在实践中,在我一生中看到的所有UNIX系统上,它们几乎都是连续的数字,通常总数不超过几百。 @mtraceur - vvaltchev
我认为一个指令的差异是因为它决定将数组设置为132个元素,其中第0个索引是第1个errno,而不是手动数组版本,其中第1个索引是第1个errno。值得注意的是,手动添加case 0: return 0并没有改变结果 - 所以我猜测优化器正在根据已经识别出零情况与任何其他越界情况相同的逻辑的抽象形式做出决策,并且这种逻辑排除了启发式地注意到一个额外的数组条目可能比一个始终执行的指令更有效率的可能性。 - mtraceur
现代 clang 9.0.1 适用于 aarch64 Linux,表现出相同的行为 - 相同的代码,但是选择从第一个 errno 开始的数组,因此需要 sub 然后 cmp 而不仅仅是 cmp - mtraceur
显示剩余5条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接