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

Tags:

Posix

Users