我经常想获得与用户 ID 关联的登录名,并且因为它被证明是一个常见的用例,所以我决定编写一个 shell 函数来做到这一点。虽然我主要使用 GNU/Linux 发行版,但我尝试将脚本编写为尽可能可移植,并检查我所做的是否与 POSIX 兼容。
/etc/passwd
我尝试的第一种方法是解析/etc/passwd
(使用awk
)。
awk -v uid="$uid" -F: '$3 == uid {print $1}' /etc/passwd
Run Code Online (Sandbox Code Playgroud)
但是,这种方法的问题在于登录可能不是本地的,例如,用户身份验证可能通过 NIS 或 LDAP。
getent
命令使用getent passwd
比解析更具可移植性,/etc/passwd
因为它还可以查询非本地 NIS 或 LDAP 数据库。
getent passwd "$uid" | cut -d: -f1
Run Code Online (Sandbox Code Playgroud)
不幸的是,该getent
实用程序似乎并未由 POSIX 指定。
id
命令id
是 POSIX 标准化实用程序,用于获取有关用户身份的数据。
BSD 和 GNU 实现接受用户 ID 作为操作数:
这意味着它可用于打印与用户 ID 关联的登录名:
id -nu "$uid"
Run Code Online (Sandbox Code Playgroud)
但是,在 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
}
Run Code Online (Sandbox Code Playgroud)
然而,这很笨重、不优雅,而且——更重要的是——不够健壮;如果用户 ID 无效或不存在,它将以非零状态退出。在我编写一个更长更笨拙的 shell 脚本来分析和存储每个命令调用的退出状态之前,我想我会在这里问:
是否有更优雅和便携(POSIX 兼容)的方式来获取与用户 ID 关联的登录名?
cas*_*cas 15
一种常见的方法是测试您想要的程序是否存在并且是否可以从您的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
}
Run Code Online (Sandbox Code Playgroud)
因为 POSIXid
不支持 UID 参数,所以 elif
for 子句id
不仅要测试是否id
在 PATH 中,还要测试它是否会无误地运行。这意味着它可能会运行id
两次,幸运的是这不会对性能产生明显影响。这也有可能是两个id
和awk
将运行,用相同的可以忽略不计的性能影响。
顺便说一句,使用这种方法,无需存储输出。只有其中一个将运行,因此只有一个将打印输出以供函数返回。
除了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
Run Code Online (Sandbox Code Playgroud)
或 Python:
if python -c 'import pwd, sys; print(pwd.getpwuid(int(sys.argv[1]))).pw_name' 2>/dev/null; then return; fi
Run Code Online (Sandbox Code Playgroud)
POSIX 指定getpwuid
为标准 C 函数,用于在用户数据库中搜索用户 ID,允许将 ID 转换为登录名。我下载了 GNU coreutils的源代码,并且可以看到这个函数正在他们的实用程序实现中使用,例如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;
}
}
Run Code Online (Sandbox Code Playgroud)
我没有机会使用 NIS/LDAP 对其进行测试,但我注意到,如果/etc/passwd
.
用法示例:
$ ./get_user ""
The login name for 0 is: root
$ ./get_user 99
Invalid user ID: 99
Run Code Online (Sandbox Code Playgroud)