POSIX Users – How to Get User Name Associated with a User ID

posixusers

I often want to get the login name associated with a user ID and because it’s proven to be a common use case, I decided to write a shell function to do this. While I primarily use GNU/Linux distributions, I try to write my scripts to be as portable as possible and to check that what I’m doing is POSIX-compatible.

Parse /etc/passwd

The first approach I tried was to parse /etc/passwd (using awk).

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

However, the problem with this approach is that the logins may not be local, e.g., user authentication could be via NIS or LDAP.

Use the getent command

Using getent passwd is more portable than parsing /etc/passwd as this also queries non-local NIS or LDAP databases.

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

Unfortunately, the getent utility does not seem to be specified by POSIX.

Use the id command

id is the POSIX-standardised utility for getting data about a user’s identity.

BSD and GNU implementations accept a User ID as an operand:

This means it can be used to print the login name associated with a User ID:

id -nu "$uid"

However, providing User IDs as the operand is not specified in POSIX; it only describes the use of a login name as the operand.

Combining all of the above

I considered combining the above three approaches into something like the following:

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
}

However, this is clunky, inelegant and – more importantly – not robust; it exits with a non-zero status if the User ID is invalid or does not exist. Before I write a longer and clunkier shell script that analyses and stores the exit status of each command invocation, I thought I’d ask here:

Is there a more elegant and portable (POSIX-compatible) way of getting the login name associated with a user ID?

Best Answer

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.

Related Question