POSIX compatible way to get user name associated with a user ID
One common way to do this is to test if the program you want exists and is available from your PATH
. For example:
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
}
Because POSIX id
doesn't support UID arguments, the elif
clause for id
has to test not only whether id
is in the PATH, but also whether it will run without error. This means it may run id
twice, which fortunately will not have a noticeable impact on performance. It is also possible that both id
and awk
will be run, with the same negligible performance hit.
BTW, with this method, there's no need to store the output. Only one of them will be run, so only one will print output for the function to return.
There's nothing in POSIX that would help other than id
. Trying id
and falling back to parsing /etc/passwd
is probably as portable as it gets in practice.
BusyBox's id
doesn't accept user IDs, but systems with BusyBox are usually autonomous embedded systems where parsing /etc/passwd
is enough.
In case you encounter a non-GNU system where id
doesn't accept user IDs, you could also try calling getpwuid
via Perl, on the chance that it's available:
username=$(perl -e 'print((getpwuid($ARGV[0]))[0])) 2>/dev/null
if [ -n "$username" ]; then echo "$username"; return; fi
Or Python:
if python -c 'import pwd, sys; print(pwd.getpwuid(int(sys.argv[1]))).pw_name' 2>/dev/null; then return; fi
POSIX specifies getpwuid
as a standard C function to search the user database for a user ID allowing the ID to be translated to a login name. I downloaded the source code for GNU coreutils and can see this function being used in their implementation of utilities such as id
and ls
.
As a learning exercise, I wrote this quick-and-dirty C program to simply act as a wrapper for this function. Bear in mind that I haven’t programmed in C since college (many years ago) and I don’t intend to use this in production but I thought I’d post it here as a proof of concept (if anyone wants to edit it, feel free):
#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;
}
}
I didn’t have the chance to test it with NIS/LDAP but I noticed that if there are multiple entries for the same user in /etc/passwd
, it ignores all but the first.
Example usage:
$ ./get_user ""
The login name for 0 is: root
$ ./get_user 99
Invalid user ID: 99