POSIX 兼容方式获取与用户 ID 关联的用户名

POSIX 兼容方式获取与用户 ID 关联的用户名

我经常想要获取与用户 ID 关联的登录名,并且因为它已被证明是一个常见用例,所以我决定编写一个 shell 函数来执行此操作。虽然我主要使用 GNU/Linux 发行版,但我尝试编写尽可能可移植的脚本,并检查我所做的事情是否与 POSIX 兼容。

解析/etc/passwd

我尝试的第一种方法是解析/etc/passwd(使用awk)。

awk -v uid="$uid" -F: '$3 == uid {print $1}' /etc/passwd

然而,这种方法的问题是登录可能不是本地的,例如,用户身份验证可以通过 NIS 或 LDAP 进行。

使用getent命令

使用getent passwd比解析更可移植,/etc/passwd因为这也会查询非本地 NIS 或 LDAP 数据库。

getent passwd "$uid" | cut -d: -f1

不幸的是,getentPOSIX 似乎没有指定该实用程序。

使用id命令

id是用于获取有关用户身份的数据的 POSIX 标准化实用程序。

BSD 和 GNU 实现接受用户 ID 作为操作数:

这意味着它可用于打印与用户 ID 关联的登录名:

id -nu "$uid"

然而,POSIX 中并未指定提供用户 ID 作为操作数;它仅描述了使用登录名作为操作数。

结合以上所有内容

我考虑将上述三种方法结合起来,如下所示:

get_username(){
    uid="$1"
    # First try using getent
    getent passwd "$uid" | cut -d: -f1 ||
        # Next try using the UID as an operand to id.
        id -nu "$uid" ||
        # As a last resort, parse `/etc/passwd`.
        awk -v uid="$uid" -F: '$3 == uid {print $1}' /etc/passwd
}

然而,这很笨重、不优雅,而且更重要的是,不够健壮。如果用户 ID 无效或不存在,它将以非零状态退出。在我编写一个更长、更笨重的 shell 脚本来分析和存储每个命令调用的退出状态之前,我想我应该在这里问:

是否有一种更优雅、更便携(POSIX 兼容)的方式来获取与用户 ID 关联的登录名?

答案1

一种常见的方法是测试您想要的程序是否存在并且可以从您的PATH.例如:

get_username(){
  uid="$1"

  # First try using getent
  if command -v getent > /dev/null 2>&1; then 
    getent passwd "$uid" | cut -d: -f1

  # Next try using the UID as an operand to id.
  elif command -v id > /dev/null 2>&1 && \
       id -nu "$uid" > /dev/null 2>&1; then
    id -nu "$uid"

  # Next try perl - perl's getpwuid just calls the system's C library getpwuid
  elif command -v perl >/dev/null 2>&1; then
    perl -e '@u=getpwuid($ARGV[0]);
             if ($u[0]) {print $u[0]} else {exit 2}' "$uid"

  # As a last resort, parse `/etc/passwd`.
  else
      awk -v uid="$uid" -F: '
         BEGIN {ec=2};
         $3 == uid {print $1; ec=0; exit 0};
         END {exit ec}' /etc/passwd
  fi
}

由于 POSIXid不支持 UID 参数,因此 eliffor 子句id不仅要测试是否id在 PATH 中,还要测试它是否能够正常运行。这意味着它可能会运行id两次,幸运的是这不会对性能产生明显的影响。也有可能同时运行id和,而性能影响同样可以忽略不计。awk

顺便说一句,使用这种方法,不需要存储输出。仅运行其中之一,因此只有其中之一会打印函数返回的输出。

答案2

POSIX 指定getpwuid作为标准 C 函数,用于在用户数据库中搜索用户 ID,从而允许将 ID 转换为登录名。我下载了GNU的源代码核心工具id并可以看到该函数在其实用程序(例如和)的实现中使用ls

作为学习练习,我编写了这个快速而肮脏的 C 程序来简单地充当该函数的包装器。请记住,自从大学(很多年前)以来,我就没有用 C 编程过,并且我不打算在生产中使用它,但我想我应该将其发布在这里作为概念证明(如果有人想编辑它) ,随意):

#include <stdio.h>
#include <stdlib.h>  /* atoi */
#include <pwd.h>

int main( int argc, char *argv[] ) {
    uid_t uid;
    if ( argc >= 2 ) {
        /* NB: atoi returns 0 (super-user ID) if argument is not a number) */
        uid = atoi(argv[1]);
    }
    /* Ignore any other arguments after the first one. */
    else {
        fprintf(stderr, "One numeric argument must be supplied.\n");
        return 1;
    }

    struct passwd *pwd;
    pwd = getpwuid(uid);
    if (pwd) {
        printf("The login name for %d is: %s\n", uid, pwd->pw_name);
        return 0;
    }
    else {
        fprintf(stderr, "Invalid user ID: %d\n", uid);
        return 1;
    }
}

我没有机会使用 NIS/LDAP 对其进行测试,但我注意到,如果 中同一用户有多个条目/etc/passwd,它会忽略除第一个以外的所有条目。

用法示例:

$ ./get_user ""
The login name for 0 is: root

$ ./get_user 99
Invalid user ID: 99

答案3

POSIX 中除了id.尝试id并返回解析/etc/passwd可能在实践中是可移植的。

BusyBoxid不接受用户 ID,但带有 BusyBox 的系统通常是自治嵌入式系统,其中解析/etc/passwd就足够了。

如果您遇到id不接受用户 ID 的非 GNU 系统,您也可以尝试调用getpwuid通过 Perl,如果它可用的话:

username=$(perl -e 'print((getpwuid($ARGV[0]))[0])) 2>/dev/null
if [ -n "$username" ]; then echo "$username"; return; fi

或者Python:

if python -c 'import pwd, sys; print(pwd.getpwuid(int(sys.argv[1]))).pw_name' 2>/dev/null; then return; fi

答案4

一般来说,我建议不要这样做。从用户名到 uids 的映射不是一对一的,并且您可以转换的编码假设从...回来获取用户名的 uid 会破坏一切。例如,我经常通过使容器中的passwd和文件将所有用户名和组名映射到 id 0 来运行完全无根的用户命名空间容器;group这使得安装包能够正常工作而不会chown失败。但是如果某些东西试图将 0 转换回 uid 并且没有得到它所期望的结果,它就会无缘无故地崩溃。因此,在此示例中,您应该转换为 uid 并在该空间中进行比较,而不是转换回并比较用户名。

如果您确实需要执行此操作,如果您是 root,则可以通过创建一个临时文件,chown将其写入 uid,然后使用ls读回并解析所有者的名称来进行半可移植。但我只会使用一种众所周知的方法,该方法不是标准化的,但“在实践中可移植”,就像您已经找到的方法之一一样。

但再次强调,不要这样做。有时,很难做的事情就是向您发送消息。

相关内容